<?xml version="1.0" encoding="utf-8"?>

<feed xmlns="http://www.w3.org/2005/Atom">
  <title>My name is Per</title>
  <subtitle>Occasionally new content. Always bad opinions.</subtitle>
  <link href="https://permortensen.com/feed.xml" rel="self"/>
  <link href="https://permortensen.com/"/>
  <updated>2026-02-01T19:32:34Z</updated>
  <id>https://permortensen.com/feed.xml</id>
  
  <entry>
    <title>Tending to my personal music library</title>
    <link href="https://permortensen.com/tending-to-my-personal-music-library/"/>
    <updated>2026-02-01T19:32:34Z</updated>
    <id>https://permortensen.com/tending-to-my-personal-music-library/</id>
    <author>
      <name>Per Mortensen</name>
    </author>
    <content type="html">&lt;p&gt;I really started listening to music after I got an iPod Video for Christmas in 2005. I used that thing daily for around a decade. Being a weird nerd, my music collection initially consisted mostly of video game soundtracks, many of which were unofficial game rips done by fans like myself. For instance, I’ve always been a fan of the soundtrack to &lt;em&gt;Croc: Legend of the Gobbos&lt;/em&gt;, a PS1 game that never got an official OST release (at least until the collector’s edition of the remaster &lt;a href=&quot;https://rockitgames.com/blogs/news/coc-legend-of-the-gobbos-january-2026-update&quot;&gt;eventually ships&lt;/a&gt;), so I made my own rip directly from the game disc. I spent a lot of time on the FFShrine forums looking for interesting soundtracks, and it’s truly a shame that &lt;a href=&quot;https://www.reddit.com/r/FFShrine/comments/e84dav/end_of_an_era/&quot;&gt;it’s no longer around&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;Around 2015, I felt smartphones had finally become good enough to replace my iPod, and they also offered new possibilities. The collection of MP3s that I had been building for a decade was uploaded in its entirety to Google Play Music, and I eventually moved to a Spotify subscription a few years later. I still wanted to listen to my weird MP3s, but importing local files to Spotify was a truly awful experience – probably because they didn’t actually want you to do it – so I soon moved to Apple Music instead. Its import feature wasn’t perfect, but it was a massive improvement coming from Spotify, almost like someone on the dev team actually cared about it.&lt;/p&gt;&lt;p&gt;I’ve been using Apple Music for around 5 years now, filling the gaps in its catalog with my own files, and overall I’ve been pretty satisfied. But as with any streaming service, when the license for a piece of media expires, you are no longer allowed to experience it. From one day to the next, your favorite song might disappear from your library without warning or recourse. It cements the fact that it’s not really &lt;em&gt;your&lt;/em&gt; library; you’re just renting it on a monthly basis. A well maintained CD or vinyl collection can last a lifetime – and digital files are basically immortal when regularly copied to new physical media – but how much of your online music collection will survive the next 5 years? 10 years? 30 years? Your Spotify or Apple Music library is ultimately ephemeral, and its lifetime is dictated by a corporate machine with no regard for sentimental value.&lt;/p&gt;&lt;p&gt;Streaming services are built for convenience, not permanence. You get a constantly updated library of popular songs available on demand, and in return you sometimes have to let go of things you’ve already listened to. It’s a tradeoff that makes sense for casual listening, but what about the music you really care about? That one song you’ve listened to on repeat for weeks, or the one you enjoy rediscovering every few years? The album that makes you reminisce about a joyful past experience? Maybe those deserve better. Wouldn’t you sleep better knowing those couldn’t be taken away by something as nebulous as record label licensing agreements? Me too.&lt;/p&gt;&lt;p&gt;After years of prioritizing convenience I’ve returned to my MP3 collection, slowly expanding it and listening to it. While MP3 files work exactly the way they did 20 years ago, the technology around them has changed for the better. VLC or &lt;a href=&quot;https://www.foobar2000.org/&quot;&gt;foobar2000&lt;/a&gt; still work great, but with the help of &lt;a href=&quot;https://www.navidrome.org/&quot;&gt;Navidrome&lt;/a&gt; or &lt;a href=&quot;https://jellyfin.org/&quot;&gt;Jellyfin&lt;/a&gt; you can get a personal, always-online streaming service without giving up on the permanence that local files provide.&lt;/p&gt;&lt;p&gt;For me, tending to my music library is a mix of nostalgia for a simpler time, digital preservation of art that matters to me, and nerding out about niche stuff like file organization and proper ID3 tags. In other words, right up my alley. There’s also a dash of self expression: I’m not building a music library that will be to everyone’s taste, but a curated, deeply personal one where every album has a reason for inclusion, or maybe even a story to tell. I find it a comforting activity, and I’m in no rush. This is a collection that’s been around for two decades, and I hope it stays around for at least another two.&lt;/p&gt;</content>
  </entry>
  
  <entry>
    <title>Replacing Backblaze</title>
    <link href="https://permortensen.com/replacing-backblaze/"/>
    <updated>2025-12-03T20:28:10Z</updated>
    <id>https://permortensen.com/replacing-backblaze/</id>
    <author>
      <name>Per Mortensen</name>
    </author>
    <content type="html">&lt;p&gt;I’ve been a Backblaze customer since 2018 when a friend recommended their &lt;a href=&quot;https://www.backblaze.com/cloud-backup/personal&quot;&gt;computer backup service&lt;/a&gt; to me, and for the most part I’ve been satisfied with it. Unlimited storage for 10€/month is pretty cheap and their software seems to work well, but that doesn’t mean a whole lot. The only people worth listening to when it comes to backup program recommendations are the ones who have successfully restored data with it in an emergency, and I’m lucky to not count myself among those. That means my general recommendation of Backblaze never carried much weight, but for what it’s worth I can recommend it no longer.&lt;/p&gt;&lt;p&gt;By complete accident, I noticed that at some point in October, Backblaze had stopped backing up files in iCloud Drive, even though I store its contents locally. For me, iCloud Drive might be one of the most important things to back up, something Backblaze has been happily doing for the past 7 years, so seeing it vanish from my backups was concerning to say the least. Looking into it I found a &lt;a href=&quot;https://www.reddit.com/r/backblaze/comments/1olk62b/psa_icloud_drive_files_are_no_longer_backed_up/&quot;&gt;Reddit thread&lt;/a&gt; explaining that &lt;a href=&quot;https://www.backblaze.com/computer-backup/docs/backup-client-release-notes-mac#release-version-9.2.2.878&quot;&gt;Backblaze version 9.2.2.878&lt;/a&gt; silently stopped backing up cloud storage folders, among other things:&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;The Backup Client now blocks SSHFS-mounted drives and excludes system and cloud-synced folders like &lt;code&gt;~/Library/CloudStorage&lt;/code&gt;, Caches, Developer, Metadata, and Mobile Documents to prevent unintended backups and reduce data usage.&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;This is a frankly &lt;em&gt;major&lt;/em&gt; change in functionality, and Backblaze apparently decided the best way to let their paying customers know was to bury it in a bullet point in a bunch of dry, technical release notes for an ordinary maintenance release of their software – under “Improvements” no less! It’s clear they’re not really interested in “preventing unintended backups” – there’s already a folder exclusion feature in Backblaze for this exact purpose – but I guess offering unlimited storage was a zero interest rate phenomenon.&lt;/p&gt;&lt;p&gt;What’s especially funny is that their own support site still has an article explaining specifically &lt;a href=&quot;https://www.backblaze.com/computer-backup/docs/back-up-icloud-drive&quot;&gt;how to back up iCloud Drive&lt;/a&gt;, even though that feature is now removed. The &lt;a href=&quot;https://www.backblaze.com/computer-backup/docs/supported-backup-data#exclusions&quot;&gt;Supported Backup Data&lt;/a&gt; article, linked to in the same release notes that , doesn’t mention cloud storage folders at all (as of 3 Dec 2025), and they’ve previously written about how &lt;a href=&quot;https://www.backblaze.com/blog/cloud-backup-vs-cloud-sync/&quot;&gt;Backblaze complements sync services&lt;/a&gt; since it also backs up cloud synced files.&lt;/p&gt;&lt;p&gt;Backblaze now does less for the same monthly price, which sucks. Having to find out by accident sucks even more. If I wasn’t some weirdo inspecting his automated backups, I would probably only find out when I needed to restore something, at which point it would no longer exist. I have more than one backup medium, of course. It’s going to be worse for those of us who have been sold the idea that Backblaze backs up your entire computer, when that claim becomes less true with each app update.&lt;/p&gt;&lt;p&gt;As I can no longer trust Backblaze to keep my data safe, I will not be renewing my subscription next month. So I found an alternative solution. The open source CLI program &lt;a href=&quot;https://restic.net/&quot;&gt;Restic&lt;/a&gt; has been on my radar for a while now because it seems to do a lot of things right:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;it’s a single binary that runs on &lt;a href=&quot;https://restic.readthedocs.io/en/stable/020_installation.html&quot;&gt;basically everything&lt;/a&gt; and uses standard backend protocols&lt;/li&gt;&lt;li&gt;its backups are encrypted, compressed, deduplicated and verifiable&lt;/li&gt;&lt;li&gt;it’s got a large community behind it making everything from &lt;a href=&quot;https://github.com/emuell/restic-browser&quot;&gt;GUI clients&lt;/a&gt; to &lt;a href=&quot;https://github.com/garethgeorge/backrest&quot;&gt;centralized orchestration tools&lt;/a&gt; to &lt;a href=&quot;https://github.com/rustic-rs/rustic&quot;&gt;re-implementations&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;As with Backblaze, my recommendation of Restic is basically irrelevant until I have experienced real disaster recovery, but what I can say is this: it’s &lt;em&gt;very&lt;/em&gt; fast at backing up, and test restores have worked as they should. It’s also likely to survive in some state or another for a long time just due to the size of the community and the distinct lack of dependencies.&lt;/p&gt;&lt;p&gt;Unlike Backblaze, Restic comes with no predetermined backup destination. While I can easily use it to back up my computer to my NAS, I also need an offsite storage location in the unlikely event that my house burns down with the NAS inside. Since Restic works great with an SFTP backend, the most appealing option is currently a &lt;a href=&quot;https://www.hetzner.com/storage/storage-box/&quot;&gt;Storage Box from Hetzner&lt;/a&gt; (coincidentally, a provider I already use for other things). It’s supposedly just consumer hardware in some sort of RAID configuration, but you get 1 TB storage space, a few automated snapshots, and unlimited traffic for 4€/month, which is decently priced. You also get limited shell access, but it’s practically useless, so consider these boxes nothing more than remote filesystems.&lt;/p&gt;&lt;p&gt;I use the excellent &lt;a href=&quot;https://creativeprojects.github.io/resticprofile/&quot;&gt;Resticprofile&lt;/a&gt; wrapper to configure and schedule backups, and since Restic repositories are just regular folders, a repository can be easily rsynced offsite. For now, my backup system is not &lt;em&gt;quite&lt;/em&gt; as smooth as the dedicated Backblaze app, and a plain 1 TB disk is not &lt;em&gt;quite&lt;/em&gt; the same as the supposedly “unlimited storage” that Backblaze advertises, but at least this system doesn’t change its rules without notice.&lt;/p&gt;</content>
  </entry>
  
  <entry>
    <title>Archiving media with Linkding</title>
    <link href="https://permortensen.com/archiving-media-with-linkding/"/>
    <updated>2025-08-16T05:56:26Z</updated>
    <id>https://permortensen.com/archiving-media-with-linkding/</id>
    <author>
      <name>Per Mortensen</name>
    </author>
    <content type="html">&lt;p&gt;Over the last year I’ve been getting into selfhosting apps on my own hardware. I’ve had a post about what that looks like in draft for a while now, and maybe you’ll see that at some point. But one thing I’m currently running is the &lt;a href=&quot;https://linkding.link/&quot;&gt;Linkding bookmark manager&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;I’ve tried &lt;a href=&quot;https://karakeep.app/&quot;&gt;Karakeep&lt;/a&gt; as well, but its UI just doesn’t feel right to me. What it does have going for it is the built in video archiving feature: if you bookmark a video, it’ll download an offline copy for you. Linkding does the same thing with HTML snapshots – and better than Karakeep, I might add – but it doesn’t treat media links in any special way, and an HMTL snapshot of a YouTube video page is pretty uninteresting.&lt;/p&gt;&lt;p&gt;Linkding has a REST API, so it’s got a sizable list of &lt;a href=&quot;https://linkding.link/community/&quot;&gt;community projects&lt;/a&gt; built around it. I’ve now made one of the projects on that list: &lt;a href=&quot;https://permortensen.com/projects/linkding-media-archiver/&quot;&gt;Linkding Media Archiver&lt;/a&gt;. It adds a Karakeep-esque video archiving feature by periodically scanning bookmarks for media links and handing them off to &lt;a href=&quot;https://github.com/yt-dlp/yt-dlp&quot;&gt;yt-dlp&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://permortensen.com/projects/linkding-media-archiver/&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://permortensen.com/assets/images/dist/oP2EClVFFH-800.webp 800w, https://permortensen.com/assets/images/dist/oP2EClVFFH-1200.webp 1200w, https://permortensen.com/assets/images/dist/oP2EClVFFH-1244.webp 1244w&quot; sizes=&quot;(max-width: 65ch) 100vw, 65ch&quot;&gt;&lt;img src=&quot;https://permortensen.com/assets/images/dist/oP2EClVFFH-800.png&quot; alt=&quot;List of Linkding bookmark assets&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1244&quot; height=&quot;320&quot; srcset=&quot;https://permortensen.com/assets/images/dist/oP2EClVFFH-800.png 800w, https://permortensen.com/assets/images/dist/oP2EClVFFH-1200.png 1200w, https://permortensen.com/assets/images/dist/oP2EClVFFH-1244.png 1244w&quot; sizes=&quot;(max-width: 65ch) 100vw, 65ch&quot;&gt;&lt;/picture&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;It adds the media files as attachments to the bookmark itself, so you don’t need a separate interface to retrieve them afterwards: it’s all there within Linkding.&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://github.com/proog/linkding-media-archiver&quot;&gt;Check it out&lt;/a&gt; if you’re a Linkding user!&lt;/p&gt;</content>
  </entry>
  
  <entry>
    <title>LOK Digital trailer theme</title>
    <link href="https://permortensen.com/lok-digital-trailer-theme/"/>
    <updated>2024-07-01T14:47:57Z</updated>
    <id>https://permortensen.com/lok-digital-trailer-theme/</id>
    <author>
      <name>Per Mortensen</name>
    </author>
    <content type="html">&lt;p&gt;On June 8 I watched the &lt;a href=&quot;https://www.youtube.com/watch?v=74SmXe-Rk3Y&quot;&gt;Summer Game Fest 2024 live stream&lt;/a&gt; which showed off a lot of interesting indie games, including the puzzle game &lt;a href=&quot;https://lok-digital.com/&quot;&gt;LOK Digital&lt;/a&gt;. The game looks cute and stylish (much like myself) but the thing that really stuck with me was the music used in &lt;a href=&quot;https://www.youtube.com/watch?v=0U79l225a9Y&quot;&gt;the trailer&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;The game isn’t out (yet) and there’s no official soundtrack to be found (yet), so I kept coming back to the trailer just to listen to it. My obsession with finding a clean version of the track led to me downloading the demo from Steam and the extracting its audio assets.&lt;/p&gt;&lt;p&gt;I found an audio track named &lt;em&gt;“X”&lt;/em&gt; which is 3 minutes long and essentially contains two parts, the latter of which is a version of the trailer theme! Unfortunately, it’s not exactly the same, as the trailer version is faster and contains a different outro. But until the official soundtrack comes out (fingers crossed!), it might be as close as we can get.&lt;/p&gt;&lt;p&gt;&lt;s&gt;I have cut it into &lt;a href=&quot;https://static.per.computer/LOK%20Digital%20-%20X%20%28part%202%29.mp3&quot;&gt;an MP3 that you can find here&lt;/a&gt;.&lt;/s&gt;&lt;/p&gt;&lt;p&gt;&lt;em&gt;Edit 2024-12-15: on December 11 the game and &lt;a href=&quot;https://blazh.bandcamp.com/album/lok-digital-official-soundtrack&quot;&gt;its official soundtrack&lt;/a&gt; was released, so go listen to that instead! The trailer theme is a sped-up version of the second half of the track called &lt;a href=&quot;https://blazh.bandcamp.com/track/sparks&quot;&gt;Sparks&lt;/a&gt;, and the rest of the soundtrack seems pretty cool too.&lt;/em&gt;&lt;/p&gt;</content>
  </entry>
  
  <entry>
    <title>Seq on Railway</title>
    <link href="https://permortensen.com/seq-on-railway/"/>
    <updated>2024-06-09T18:47:08Z</updated>
    <id>https://permortensen.com/seq-on-railway/</id>
    <author>
      <name>Per Mortensen</name>
    </author>
    <content type="html">&lt;p&gt;I’m using &lt;a href=&quot;https://railway.app&quot;&gt;Railway&lt;/a&gt; for some side projects. It’s like Netlify for traditional, non-serverless web apps, and it works really well: I can just push to git and the code is built and deployed, without having to worry about hosting infrastructure. It even supports structured logging by reading JSON-formatted logs directly from stdout.&lt;/p&gt;&lt;p&gt;But Railway’s built-in log explorer is pretty rudimentary, so you might want something more advanced if you’re dealing with a lot of logs. At work, we use &lt;a href=&quot;https://datalust.co/seq&quot;&gt;Seq&lt;/a&gt; for application logging, and it too works really well: I can query structured logs with a flexible query language, set up dashboards and alerts, plot occurrences over time, and so on.&lt;/p&gt;&lt;p&gt;But until now, deploying Seq on Railway has been a bit of a pain. While Seq has an official Docker image, it needs a little configuration to make it work with Railway’s port binding and private networking features. My new template should make this process easier:&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://railway.app/template/TgfPHD?referralCode=LnlNNY&quot;&gt;&lt;img src=&quot;https://railway.app/button.svg&quot; alt=&quot;Deploy on Railway&quot;&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;It contains a single service that spins up the Seq image from Docker Hub, but with some preconfigured options that should make it &lt;em&gt;just work&lt;/em&gt;, as any Railway user would expect 😉&lt;/p&gt;</content>
  </entry>
  
  <entry>
    <title>Adding Pagefind to an Eleventy site</title>
    <link href="https://permortensen.com/adding-pagefind-to-an-eleventy-site/"/>
    <updated>2023-12-06T19:05:51Z</updated>
    <id>https://permortensen.com/adding-pagefind-to-an-eleventy-site/</id>
    <author>
      <name>Per Mortensen</name>
    </author>
    <content type="html">&lt;p&gt;I just added &lt;a href=&quot;https://pagefind.app/&quot;&gt;Pagefind&lt;/a&gt; search to this site. Although it only just got bumped to stable, Pagefind is an elegant tool that solves a specific problem very well: adding search to static sites. It even comes with a decent default UI component.&lt;/p&gt;&lt;p&gt;This is a quick note on how to integrate it into an Eleventy site. There are already some posts on the subject but they all boil down to &lt;em&gt;&lt;code&gt;npx&lt;/code&gt; the Pagefind CLI in the &lt;code&gt;eleventy.after&lt;/code&gt; build event.&lt;/em&gt; The CLI is fine if the site is large enough that indexing during development is annoying, but at that point it’s simpler to just run it with a &lt;code&gt;package.json&lt;/code&gt; script. For 95% of sites, we can do better.&lt;/p&gt;&lt;p&gt;We’re still going to use the &lt;code&gt;eleventy.after&lt;/code&gt; event, but we’re going to use the actual &lt;a href=&quot;https://pagefind.app/docs/node-api/&quot;&gt;Pagefind Node.js API&lt;/a&gt; instead of executing shell commands. Pagefind needs to be imported asynchronously as it’s not a CommonJS module, but that’s really the only quirk you have to get past. &lt;a href=&quot;https://github.com/11ty/eleventy/pull/3074&quot;&gt;Eleventy 3.0 will support ESM&lt;/a&gt;, so in turn you’ll be able to import it with regular &lt;code&gt;import&lt;/code&gt; statements.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;const path = require(&amp;quot;path&amp;quot;);

eleventyConfig.on(&amp;quot;eleventy.after&amp;quot;, async function ({ dir }) {
  const inputPath = dir.output;
  const outputPath = path.join(dir.output, &amp;quot;pagefind&amp;quot;);

  console.log(&amp;quot;Creating Pagefind index of %s&amp;quot;, inputPath);

  const pagefind = await import(&amp;quot;pagefind&amp;quot;);
  const { index } = await pagefind.createIndex();
  const { page_count } = await index.addDirectory({ path: inputPath });
  await index.writeFiles({ outputPath });

  console.log(
    &amp;quot;Created Pagefind index of %i pages in %s&amp;quot;,
    page_count,
    outputPath
  );
});
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The API uses mostly the same defaults as the CLI so little configuration is needed. In this example we’re just indexing a directory of HTML files, but integrating Pagefind like this allows advanced usage as well. You could, for instance, customize file indexing on the fly using a &lt;a href=&quot;https://www.11ty.dev/docs/config/#linters&quot;&gt;linter&lt;/a&gt;, or index arbitrary data with &lt;a href=&quot;https://pagefind.app/docs/node-api/#indexaddcustomrecord&quot;&gt;custom records&lt;/a&gt;. Here’s an example that combines these to index specific parts of XML templates.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;const path = require(&amp;quot;path&amp;quot;);

let index;

eleventyConfig.on(&amp;quot;eleventy.before&amp;quot;, async function () {
  const pagefind = await import(&amp;quot;pagefind&amp;quot;);
  index = (await pagefind.createIndex()).index;
});

eleventyConfig.addLinter(&amp;quot;index-content&amp;quot;, async function (content) {
  if (this.outputPath?.endsWith(&amp;quot;.xml&amp;quot;)) {
    await index.addCustomRecord({
      url: this.page.url,
      content: getSearchableText(content), // Some assembly required
      language: &amp;quot;en&amp;quot;,
      meta: { title: getTitle(content) },
    });
  }
});

eleventyConfig.on(&amp;quot;eleventy.after&amp;quot;, async function ({ dir }) {
  const outputPath = path.join(dir.output, &amp;quot;pagefind&amp;quot;);
  await index.writeFiles({ outputPath });
});
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Then it’s just a matter of &lt;a href=&quot;https://pagefind.app/docs/ui-usage/&quot;&gt;adding the default UI&lt;/a&gt; somewhere and everything will just work™ when building the site – even in &lt;code&gt;--serve&lt;/code&gt; mode.&lt;/p&gt;</content>
  </entry>
  
  <entry>
    <title>Building for Gemini with Eleventy</title>
    <link href="https://permortensen.com/building-for-gemini-with-eleventy/"/>
    <updated>2023-11-08T21:24:54Z</updated>
    <id>https://permortensen.com/building-for-gemini-with-eleventy/</id>
    <author>
      <name>Per Mortensen</name>
    </author>
    <content type="html">&lt;p&gt;I recently discovered &lt;a href=&quot;https://geminiprotocol.net/&quot;&gt;Gemini&lt;/a&gt;, an interesting lightweight alternative to HTTP, complete with its own markup language called &lt;a href=&quot;https://geminiprotocol.net/docs/gemtext.gmi&quot;&gt;Gemtext&lt;/a&gt;. While it’s only a couple of years old at this point, it’s supposedly inspired by the Gopher protocol. I’ll have to take their word for it – Gopher died off before I started using computers. I love the possibilities afforded by the modern web, but I can understand yearning for an online experience that isn’t dominated by 25 MB page loads and cookie consent popups. Sometimes I wish I could browse the web entirely in Reader mode, and Gemini basically says &lt;em&gt;“what if everything was like that by default?”&lt;/em&gt; It’s too minimalist to ever take off – &lt;a href=&quot;https://www.theregister.com/2022/01/27/gemini_protocol/&quot;&gt;and arguably too elitist&lt;/a&gt; – but Gemini is an interesting vision of what the web could have been if only we as a species could settle for less.&lt;/p&gt;&lt;p&gt;As a side project, I wanted to try bringing permortensen.com into Geminispace – a &lt;em&gt;capsule&lt;/em&gt; in local parlance. I already use a static site generator called &lt;a href=&quot;https://www.11ty.dev/&quot;&gt;Eleventy&lt;/a&gt; to build this site, which is mostly a bunch of Markdown files and some HTML layouts. Eleventy usually transforms content into HTML, but it’s flexible enough to output pretty much any format – like Gemtext – if you tweak it enough. Some requirements for the Gemtext version:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;It has to use the same Markdown files without modification, as I don’t want to update every page and blog post&lt;/li&gt;&lt;li&gt;It has to slot into my existing &lt;code&gt;.eleventy.js&lt;/code&gt; configuration and be enabled with a single toggle, like an environment variable&lt;/li&gt;&lt;li&gt;Both HTML and Gemtext builds have to produce a ready-to-publish output directory, as I don’t want any extra steps to my deployment workflow (I just push to &lt;code&gt;main&lt;/code&gt;)&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;The rest of this post documents the process of adapting my site for Gemini. I couldn’t find any existing guides on the topic, so I wanted to share what I found out in case anyone else finds it interesting. Geminispace could probably use some more content…&lt;/p&gt;&lt;h2&gt;Project layout&lt;/h2&gt;&lt;p&gt;For the existing HTML site, my source directory structure looks something like this&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;permortensen.com/
  _layouts/
    default.html
    post.html
  _posts/
    post1.md
    post2.md
    ...
  .eleventy.js
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Every post uses a layout template called &lt;code&gt;post&lt;/code&gt;, which renders the title and date from the post’s front matter. This template itself uses a layout called &lt;code&gt;default&lt;/code&gt;, which renders the &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt; elements around it. These &lt;code&gt;.html&lt;/code&gt; files are really &lt;a href=&quot;https://shopify.github.io/liquid/&quot;&gt;Liquid&lt;/a&gt; templates that &lt;em&gt;just happen&lt;/em&gt; to contain HTML. The &lt;code&gt;.md&lt;/code&gt; files also contain some Liquid tags, such as for &lt;a href=&quot;https://www.11ty.dev/docs/plugins/image/&quot;&gt;eleventy-img&lt;/a&gt;, so they are preprocessed with Liquid as well. This works out of the box, as Eleventy preprocesses both HTML and Markdown files with &lt;a href=&quot;https://liquidjs.com/&quot;&gt;LiquidJS&lt;/a&gt; by default (probably to align with &lt;a href=&quot;https://jekyllrb.com/&quot;&gt;Jekyll&lt;/a&gt;, the OG static site generator).&lt;/p&gt;&lt;h2&gt;About Gemtext conversion&lt;/h2&gt;&lt;p&gt;Like HTML 30 years ago, Gemtext is all about documents linking to other documents, but with a tiny vocabulary. Whereas all of Markdown can translate into HTML, only a fraction of it can translate to Gemtext. There is no syntax for images, text decoration, or even inline links – every link has to occupy its own line, and its solution to showing an image is simply linking to one. These limitations make for a very consistent browsing experience but also makes Gemtext conversion a lossy operation, even if you’re using the absolute basics of Markdown syntax.&lt;/p&gt;&lt;p&gt;The most promising Markdown to Gemtext converter I’ve found is &lt;a href=&quot;https://github.com/makew0rld/md2gemini&quot;&gt;md2gemini&lt;/a&gt;, which is a Python module available on &lt;a href=&quot;https://pypi.org/project/md2gemini/&quot;&gt;PyPI&lt;/a&gt;. It’s archived, but as of writing it still seems to work pretty well – I guess neither Markdown nor Gemtext have changed significantly since. Honorable mention to &lt;a href=&quot;https://sr.ht/~kota/gemgen/&quot;&gt;gemgen&lt;/a&gt;, which is similar but unfortunately has some bugs when rendering lists with links. Both projects do clever things to work around the limitations of Gemtext: Markdown links inside a paragraph are extracted and listed below it with their original label preserved, while images are replaced with an image link labeled by its alt text. It’s probably the best automated approach to this problem, though it can lead to some weird looking links when pulled out of their original context.&lt;/p&gt;&lt;p&gt;For instance, the paragraph you’re reading right now has &lt;a href=&quot;https://example.com/&quot;&gt;a link in it&lt;/a&gt;. That would end up as the Gemtext&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-gemtext&quot;&gt;For instance, the paragraph you&#39;re reading right now has a link in it. That would end up as the Gemtext

=&amp;gt; https://example.com/    a link in it
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;which isn’t ideal. Some would argue that this is the wrong way to label your links anyway (and I would agree!), but it probably needs some getting used to even for hyperlink purists. md2gemini can also extract links as numbered footnotes, but then &lt;em&gt;every&lt;/em&gt; link is unreadable on its own.&lt;/p&gt;&lt;p&gt;md2gemini can be installed with &lt;code&gt;pip install md2gemini&lt;/code&gt;, if you have &lt;a href=&quot;https://www.python.org/&quot;&gt;Python 3&lt;/a&gt; installed. As Eleventy runs on Node, there’s no built-in way to bundle Python modules. My current approach is to just install md2gemini globally with pip, which is not ideal. You could presumably install it using a Python package manager invoked from a &lt;code&gt;package.json&lt;/code&gt; post-install script, but then you’d still need to install Python. However you do it, the rest of this post assumes a working installation of md2gemini.&lt;/p&gt;&lt;h2&gt;Generating Gemtext from Markdown content&lt;/h2&gt;&lt;p&gt;Eleventy by default uses &lt;a href=&quot;https://github.com/markdown-it/markdown-it&quot;&gt;markdown-it&lt;/a&gt; to turn Markdown into HTML, but we now want md2gemini to turn Markdown into Gemtext instead. This is possible &lt;a href=&quot;https://www.11ty.dev/docs/languages/custom/#overriding-a-built-in-template-language&quot;&gt;by overriding the engine for md files&lt;/a&gt;, passing Markdown to a child process through STDIN, and reading back Gemtext from STDOUT. Here I’m using the &lt;code&gt;--plain&lt;/code&gt; switch to remove any inline text decoration syntax, which will otherwise render literally in Gemtext.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;const { execFile } = require(&amp;quot;node:child_process&amp;quot;);

eleventyConfig.addExtension(&amp;quot;md&amp;quot;, {
  outputFileExtension: &amp;quot;gmi&amp;quot;,
  compile: async function (inputContent) {
    return async (data) =&amp;gt; {
      const gemtextContent = await new Promise((resolve, reject) =&amp;gt; {
        const process = execFile(
          &amp;quot;md2gemini&amp;quot;,
          [&amp;quot;--plain&amp;quot;, &amp;quot;--links&amp;quot;, &amp;quot;copy&amp;quot;],
          (error, stdout) =&amp;gt; (error ? reject(error) : resolve(stdout))
        );
        process.stdin.end(inputContent);
      });

      return gemtextContent;
    };
  },
});
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Unfortunately, overriding the default Markdown engine also disables the automatic Liquid preprocessing, so the output will contain raw Liquid syntax, if any. To fix this, we have to manually invoke LiquidJS, which will render to real Markdown. I can’t find a way to grab the built-in LiquidJS instance, so as a workaround we can &lt;a href=&quot;https://www.11ty.dev/docs/languages/liquid/#optional-set-your-own-library-instance&quot;&gt;tell Eleventy to use a custom instance&lt;/a&gt; and then use it to preprocess the templates ourselves. Eleventy will register any custom tags and filters on the instance you give it, so they should work the same in both built-ins and our custom Markdown-to-Gemtext engine.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;const { execFile } = require(&amp;quot;node:child_process&amp;quot;);
const { Liquid } = require(&amp;quot;liquidjs&amp;quot;);
const liquid = new Liquid();

eleventyConfig.setLibrary(&amp;quot;liquid&amp;quot;, liquid);

// eleventyConfig.addFilter(&#39;post_url&#39;, ...);
// eleventyConfig.addShortcode(&#39;image&#39;, ...);

eleventyConfig.addExtension(&amp;quot;md&amp;quot;, {
  outputFileExtension: &amp;quot;gmi&amp;quot;,
  compileOptions: {
    permalink: &amp;quot;raw&amp;quot;,
  },
  compile: async function (inputContent) {
    return async (data) =&amp;gt; {
      const markdownContent = await liquid.parseAndRender(inputContent, data);
      const gemtextContent = await new Promise((resolve, reject) =&amp;gt; {
        const process = execFile(
          &amp;quot;md2gemini&amp;quot;,
          [&amp;quot;--plain&amp;quot;, &amp;quot;--links&amp;quot;, &amp;quot;copy&amp;quot;],
          (error, stdout) =&amp;gt; (error ? reject(error) : resolve(stdout))
        );
        process.stdin.end(markdownContent);
      });

      return gemtextContent;
    };
  },
});
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;By default, Eleventy will also attempt to render the permalink of each page using the page’s template engine to allow for dynamic permalinks. While it would be feasible to pass the links through Liquid alone (and skip Gemtext conversion), I’m not using dynamic permalinks and set &lt;code&gt;permalink: &amp;quot;raw&amp;quot;&lt;/code&gt; to disable the feature altogether.&lt;/p&gt;&lt;p&gt;Now we’re transforming Markdown to (pretty decent) Gemtext. &lt;code&gt;_site&lt;/code&gt; contains a bunch of &lt;code&gt;.gmi&lt;/code&gt; files as expected, but they all contain a bunch of HTML around our content. What gives?&lt;/p&gt;&lt;h2&gt;Replacing HTML layouts&lt;/h2&gt;&lt;p&gt;It’s the layouts. While the Markdown files now transform into Gemtext, the layouts are written in HTML and can’t meaningfully reduce to Gemtext. We’ll have to replace the HTML layouts with Gemtext equivalents, by hand. For instance, &lt;code&gt;_layouts/post.html&lt;/code&gt; gets a sibling called &lt;code&gt;_layouts/post.gmi&lt;/code&gt;.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-liquid&quot;&gt;---
layout: default
---
# {{ title }}

Posted {{ page.date | date }}

{{ content }}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;These are still just Liquid templates, except now they happen to contain Gemtext instead of HTML. As Gemtext has no concept of metadata or graphical layout, &lt;code&gt;default.gmi&lt;/code&gt; will likely just consist of &lt;code&gt;{{ content }}&lt;/code&gt; and perhaps some primary navigation links.&lt;/p&gt;&lt;p&gt;To make Eleventy use the &lt;code&gt;.gmi&lt;/code&gt; layouts instead of the &lt;code&gt;.html&lt;/code&gt; ones, we register &lt;code&gt;gmi&lt;/code&gt; as an alias of &lt;code&gt;liquid&lt;/code&gt; to get automatic Liquid rendering for &lt;code&gt;.gmi&lt;/code&gt; files. We also remove &lt;code&gt;html&lt;/code&gt; from the list of valid template formats in order to avoid processing the HTML layouts altogether. I haven’t found a way to actually &lt;em&gt;remove&lt;/em&gt; it other than setting the template formats to a list where &lt;code&gt;html&lt;/code&gt; is absent.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;eleventyConfig.addExtension(&amp;quot;gmi&amp;quot;, { key: &amp;quot;liquid&amp;quot; });
eleventyConfig.setTemplateFormats([&amp;quot;md&amp;quot;, &amp;quot;liquid&amp;quot;]);
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Assuming all layouts are referenced without a file extension, these two lines replaces every HTML layout with its Gemtext equivalent without any modifications to content or front matter. Neat!&lt;/p&gt;&lt;h2&gt;Odds and ends, bits and bobs&lt;/h2&gt;&lt;p&gt;You might have noticed that I’ve been using only the configuration API for the entire thing, and there’s a reason for that. With this centralized approach, it’s straightforward to toggle Gemini behavior on and off in &lt;code&gt;.eleventy.js&lt;/code&gt;, like with this environment variable.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;module.exports = function (eleventyConfig) {
  // Common configuration here

  if (process.env.GEMINI) {
    // Gemini specific configuration here
  } else {
    // HTML specific configuration here
  }
};
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Other things to consider customizing via this toggle:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;CSS, as you can’t style Gemtext pages&lt;/li&gt;&lt;li&gt;Client-side JavaScript&lt;/li&gt;&lt;li&gt;Favicons and Open Graph images&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;If you’re publishing an RSS/Atom feed, there is &lt;a href=&quot;https://geminiprotocol.net/docs/companion/subscription.gmi&quot;&gt;a proposed convention for a Gemini equivalent&lt;/a&gt;, but some clients like &lt;a href=&quot;https://gmi.skyjake.fi/lagrange/&quot;&gt;Lagrange&lt;/a&gt; will automatically convert a regular feed for you. Such a feed will contain HTML links unless customized, which could be done by copying the toggle to a &lt;a href=&quot;https://www.11ty.dev/docs/data-js/#example-exposing-environment-variables&quot;&gt;global JavaScript data file&lt;/a&gt; or &lt;a href=&quot;https://www.11ty.dev/docs/data-global-custom/&quot;&gt;configuration global data&lt;/a&gt; and then adapting the feed template accordingly.&lt;/p&gt;&lt;p&gt;eleventy-img generates HTML by default, which needs to be replaced. Gemtext conversion will turn any Markdown images into links, so &lt;code&gt;{% image ... %}&lt;/code&gt; shortcodes should render as regular Markdown images instead of &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; or &lt;code&gt;&amp;lt;picture&amp;gt;&lt;/code&gt; tags.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;eleventyConfig.addShortcode(&amp;quot;image&amp;quot;, async (src, alt) =&amp;gt; {
  const pluginImage = require(&amp;quot;@11ty/eleventy-img&amp;quot;);
  const metadata = await pluginImage(src);

  if (process.env.GEMINI) {
    const data = metadata.jpeg[metadata.jpeg.length - 1];
    return `![${alt}](${data.url})`;
  }

  return pluginImage.generateHTML(metadata);
});
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;As neither Markdown nor Gemtext supports &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Learn/HTML/Multimedia_and_embedding/Responsive_images&quot;&gt;responsive images&lt;/a&gt;, we’ll have to settle on a single image format – here I’ve chosen the highest quality JPEG for maximum compatibility.&lt;/p&gt;&lt;p&gt;Eleventy has a built-in convention where if a page’s permalink ends with &lt;code&gt;/index.html&lt;/code&gt;, &lt;a href=&quot;https://www.11ty.dev/docs/permalinks/#changing-the-output-location&quot;&gt;the filename is automatically removed&lt;/a&gt; for a nicer looking URL. This is HTML specific, so generated links, as well as the built-in &lt;code&gt;page.url&lt;/code&gt; property, will end in &lt;code&gt;/index.gmi&lt;/code&gt;. The behavior can be replicated for Gemtext using the (poorly documented) &lt;a href=&quot;https://www.11ty.dev/docs/permalinks/#mapping-one-url-to-multiple-files-for-internationalization&quot;&gt;URL transform feature&lt;/a&gt;.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;eleventyConfig.addUrlTransform(({ url }) =&amp;gt; {
  const pattern = /&#92;/index&#92;.gmi$/i;
  return url.match(pattern) ? url.replace(pattern, &amp;quot;/&amp;quot;) : undefined;
});
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Eleventy is very flexible and no two setups are the same. There’s probably a bunch of edge cases that I haven’t considered (or even encountered), but this should get you most of the way.&lt;/p&gt;&lt;h2&gt;Hosting and deployment&lt;/h2&gt;&lt;p&gt;We’re spoiled for choice when it comes to web hosting. &lt;a href=&quot;https://www.netlify.com/&quot;&gt;Netlify&lt;/a&gt;, &lt;a href=&quot;https://vercel.com/&quot;&gt;Vercel&lt;/a&gt;, &lt;a href=&quot;https://pages.cloudflare.com/&quot;&gt;Cloudflare Pages&lt;/a&gt;, and countless others make static website hosting a breeze by automatically building and deploying on every git push. While these services could easily build a Gemini capsule, they wouldn’t be able to host it. With them, HTTP is not only the default. It’s the only protocol in town. Gemini is a different protocol altogether, which is not supported by any of the mainstream providers and pretty much requires a box running some sort of Gemini server program (&lt;a href=&quot;https://github.com/kr1sp1n/awesome-gemini#servers&quot;&gt;which there is no shortage of&lt;/a&gt;).&lt;/p&gt;&lt;p&gt;You could do this on a Raspberry Pi or your home computer if you really wanted to, and it would certainly fit the “smol web” ethos. My current approach is a $5 &lt;a href=&quot;https://www.linode.com/&quot;&gt;Linode&lt;/a&gt; VM that runs &lt;a href=&quot;https://github.com/mbrubeck/agate&quot;&gt;Agate&lt;/a&gt; in a tmux session, coupled with a GitHub Actions workflow that builds the site with &lt;code&gt;GEMINI=1&lt;/code&gt; and deploys it with rsync. It’s the simplest approach I could find that doesn’t require me to do anything extra to deploy: I just push to &lt;code&gt;main&lt;/code&gt; and the site updates itself in turn. The following workflow is inspired by &lt;a href=&quot;https://zellwk.com/blog/github-actions-deploy/&quot;&gt;a good blog post on the topic&lt;/a&gt;.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;on:
  push:
    branches: [&amp;quot;main&amp;quot;]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: &amp;quot;npm&amp;quot;
      - uses: actions/setup-python@v4
        with:
          python-version: &amp;quot;3.9&amp;quot;
      - uses: shimataro/ssh-key-action@v2
        with:
          key: ${{ secrets.GEMINI_SSH_KEY }}
          known_hosts: ${{ secrets.GEMINI_KNOWN_HOSTS }}
      - run: pip install md2gemini
      - run: npm ci
      - run: npx eleventy
        env:
          GEMINI: 1
          NODE_ENV: production
      - run: rsync -avh --delete ./_site/ $HOST:$CONTENT/
        env:
          HOST: ${{ vars.GEMINI_HOST }}
          CONTENT: ${{ vars.GEMINI_CONTENT }}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;So for now, permortensen.com has a presence in Geminispace as well. If I’m still hosting the Gemini capsule by the time you read this – and I haven’t broken it somehow – you can find it at &lt;a href=&quot;gemini://gemini.permortensen.com/&quot;&gt;gemini.permortensen.com&lt;/a&gt;. Adapting the site for an alternative platform has been a fun challenge, and I hope this post inspires you to give it a shot as well.&lt;/p&gt;</content>
  </entry>
  
  <entry>
    <title>The semi-automatic robot vacuum</title>
    <link href="https://permortensen.com/the-semi-automatic-robot-vacuum/"/>
    <updated>2023-07-17T17:36:02Z</updated>
    <id>https://permortensen.com/the-semi-automatic-robot-vacuum/</id>
    <author>
      <name>Per Mortensen</name>
    </author>
    <content type="html">&lt;p&gt;I was recently given a &lt;a href=&quot;https://us.roborock.com/pages/roborock-s7&quot;&gt;Roborock S7&lt;/a&gt; robot vacuum cleaner. It’s an indulgence, really. We’ve got 78 m2 of floor space so automated vacuuming really isn’t necessary. But for the first time in years, I’m living in a place that uses one continuous floor with no steps between rooms, which is the ideal condition for such a device. We’ve nicknamed it &lt;em&gt;Rocky&lt;/em&gt; after it started — and lost — a fight with our clothes drying rack.&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://permortensen.com/assets/images/dist/DHBzVnYv6--956.jpeg&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://permortensen.com/assets/images/dist/DHBzVnYv6--800.webp 800w, https://permortensen.com/assets/images/dist/DHBzVnYv6--956.webp 956w&quot; sizes=&quot;(max-width: 65ch) 100vw, 65ch&quot;&gt;&lt;img src=&quot;https://permortensen.com/assets/images/dist/DHBzVnYv6--800.jpeg&quot; alt=&quot;Rocky meeting his match&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;956&quot; height=&quot;965&quot; srcset=&quot;https://permortensen.com/assets/images/dist/DHBzVnYv6--800.jpeg 800w, https://permortensen.com/assets/images/dist/DHBzVnYv6--956.jpeg 956w&quot; sizes=&quot;(max-width: 65ch) 100vw, 65ch&quot;&gt;&lt;/picture&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://permortensen.com/smart-home-automations/&quot;&gt;As previously noted&lt;/a&gt;, I use Home Assistant to tie my smart home stuff together. When I got Rocky, the only way to connect it to Home Assistant was through the &lt;a href=&quot;https://www.home-assistant.io/integrations/xiaomi_miio/#xiaomi-mi-robot-vacuum&quot;&gt;Xiaomi Miio integration&lt;/a&gt;, which required using the Mi Home app rather than the Roborock one. The 2023.5 release of Home Assistant introduced a &lt;a href=&quot;https://www.home-assistant.io/integrations/roborock&quot;&gt;dedicated Roborock integration&lt;/a&gt; which is… mediocre. It actually seems to have a lot fewer features than the old one, so switching was a bit underwhelming. But they’re promising more features down the road, and I suppose there’s now a mop intensity dropdown available.&lt;/p&gt;&lt;p&gt;Of course, you don’t integrate with Home Assistant for manual control; it’s the potential for automation that makes it worthwhile. My dream was to make Rocky totally autonomous: I would have it clean the entire apartment every few days when no one was home, requiring no effort on my part. It would be a real home of the future. It’s technically possible to do, but the reality is that we’re giant slobs who leave our clothes all over the bedroom floor, and despite his willingness to try, Rocky can’t actually swallow a pair of jeans. Oh, and I guess the dustbin needs to be emptied once in a while, as I’m too much of a cheapskate to get the self-emptying dock. Yes, there’s a limit to my indulgences.&lt;/p&gt;&lt;p&gt;So I have to actually know when the thing will run so I can prepare the place. What I really needed was a semi-automatic automation that could start cleaning when no one is home, but required a manual go-ahead first. And it should allow skipping some rooms to avoid another altercation with the drying rack. With the current state of the Roborock integration, getting this in order was a multi-step process that involved a heavily templated script and some helper entities. I’m gonna explain how I did it here.&lt;/p&gt;&lt;h2&gt;Helper entities&lt;/h2&gt;&lt;p&gt;First, I set up a number of &lt;a href=&quot;https://www.home-assistant.io/integrations/input_boolean/&quot;&gt;boolean helpers&lt;/a&gt; that control which rooms should be included when cleaning, as well as an &lt;em&gt;Auto clean&lt;/em&gt; boolean that is used as a condition for the automation to run (which we’ll get to). It looks like this on my Lovelace dashboard:&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://permortensen.com/assets/images/dist/KgOVJjur4h-942.png&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://permortensen.com/assets/images/dist/KgOVJjur4h-800.webp 800w, https://permortensen.com/assets/images/dist/KgOVJjur4h-942.webp 942w&quot; sizes=&quot;(max-width: 65ch) 100vw, 65ch&quot;&gt;&lt;img src=&quot;https://permortensen.com/assets/images/dist/KgOVJjur4h-800.png&quot; alt=&quot;Boolean helpers in Lovelace&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;942&quot; height=&quot;872&quot; srcset=&quot;https://permortensen.com/assets/images/dist/KgOVJjur4h-800.png 800w, https://permortensen.com/assets/images/dist/KgOVJjur4h-942.png 942w&quot; sizes=&quot;(max-width: 65ch) 100vw, 65ch&quot;&gt;&lt;/picture&gt;&lt;/a&gt;&lt;/p&gt;&lt;h2&gt;Selective room cleaning script&lt;/h2&gt;&lt;p&gt;The helpers are used in a script that sends a “start cleaning” command to the Roborock integration. This command is either a “selective room cleaning” command if any of the room toggles are on, or a general “clean everything” command otherwise. Selective room cleaning requires figuring out technical identifiers for each room, which is currently &lt;a href=&quot;https://www.home-assistant.io/integrations/roborock#how-can-i-clean-a-specific-room&quot;&gt;an elaborate process&lt;/a&gt;, so that’s the first step.&lt;/p&gt;&lt;p&gt;The following script uses an unsightly mix of YAML and Jinja syntax to transform the states of the room booleans into a list of Roborock room identifiers for the &lt;code&gt;app_segment_clean&lt;/code&gt; command. I’m abusing the Jinja templating to write out a string that happens to be parseable a YAML list, by concatenating comma-separated numbers and then chopping off the final comma.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;alias: Selective room cleaning
sequence:
  - if:
      - condition: or
        conditions:
          - condition: state
            entity_id: input_boolean.clean_office
            state: &amp;quot;on&amp;quot;
          - condition: state
            entity_id: input_boolean.clean_living_room
            state: &amp;quot;on&amp;quot;

          # ... other rooms omitted ... #
    then:
      - service: vacuum.send_command
        data:
          command: app_segment_clean
          params:
            - segments: |
                {%- set rooms = &amp;quot;&amp;quot; -%}
                {%- if is_state(&#39;input_boolean.clean_office&#39;, &#39;on&#39;) -%}  
                  {%- set rooms = rooms + &amp;quot;16,&amp;quot; -%}
                {%- endif -%}
                {%- if is_state(&#39;input_boolean.clean_living_room&#39;, &#39;on&#39;) -%}
                  {%- set rooms = rooms + &amp;quot;17,&amp;quot; -%}
                {%- endif -%}

                # ... other rooms omitted ... #

                [ {{ rooms[:-1] }} ]
            - repeat: 1
        target:
          device_id: 9b283511d92e17bf340dbeb97fc90d3e
    else:
      - device_id: 9b283511d92e17bf340dbeb97fc90d3e
        domain: vacuum
        entity_id: vacuum.rocky
        type: clean
mode: single
&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;Automating script execution&lt;/h2&gt;&lt;p&gt;The final step is invoking the script from an automation. It also turns off the helper so it won’t run again without manual intervention. I’ve currently set it to run when all of the following are true:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;The home has been vacant for 10 minutes&lt;/li&gt;&lt;li&gt;The &lt;em&gt;Auto clean&lt;/em&gt; helper is on&lt;/li&gt;&lt;li&gt;Rocky is docked&lt;/li&gt;&lt;/ul&gt;&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;alias: &amp;quot;Away for 10 minutes: start cleaning&amp;quot;
trigger:
  - platform: numeric_state
    entity_id: zone.home
    below: 1
    for:
      hours: 0
      minutes: 10
      seconds: 0
condition:
  - condition: state
    entity_id: input_boolean.auto_clean
    state: &amp;quot;on&amp;quot;
    enabled: true
  - condition: device
    device_id: 9b283511d92e17bf340dbeb97fc90d3e
    domain: vacuum
    entity_id: vacuum.rocky
    type: is_docked
action:
  - service: script.turn_on
    data: {}
    target:
      entity_id: script.selective_room_cleaning
    enabled: true
  - service: input_boolean.turn_off
    data: {}
    target:
      entity_id: input_boolean.auto_clean
    enabled: true
mode: single
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;With all this, I can set Rocky up for success by moving stuff off the floor and turning on &lt;em&gt;Auto clean&lt;/em&gt;, which starts cleaning once everyone has left. Is this too much work for something like this? Yes. But overcomplicating is kinda my thing.&lt;/p&gt;</content>
  </entry>
  
  <entry>
    <title>Obsidian Trash Explorer</title>
    <link href="https://permortensen.com/obsidian-trash-explorer/"/>
    <updated>2022-11-13T17:15:01Z</updated>
    <id>https://permortensen.com/obsidian-trash-explorer/</id>
    <author>
      <name>Per Mortensen</name>
    </author>
    <content type="html">&lt;p&gt;Just a quick note that I’ve published my first &lt;a href=&quot;https://obsidian.md&quot;&gt;Obsidian&lt;/a&gt; plugin: Obsidian Trash Explorer.&lt;/p&gt;&lt;p&gt;I still use &lt;a href=&quot;https://bear.app/&quot;&gt;Bear&lt;/a&gt; as my primary notes app, but I’m getting increasingly interested in Obsidian as the plugin ecosystem gains traction and the UX continues to improve. The app recently had a major visual overhaul for &lt;a href=&quot;https://obsidian.md/1.0&quot;&gt;version 1.0&lt;/a&gt;, which made me take a closer look.&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://permortensen.com/projects/obsidian-trash-explorer/&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://permortensen.com/assets/images/dist/BBIRXElP7x-800.webp 800w, https://permortensen.com/assets/images/dist/BBIRXElP7x-1200.webp 1200w, https://permortensen.com/assets/images/dist/BBIRXElP7x-1630.webp 1630w&quot; sizes=&quot;(max-width: 65ch) 100vw, 65ch&quot;&gt;&lt;img src=&quot;https://permortensen.com/assets/images/dist/BBIRXElP7x-800.png&quot; alt=&quot;Obsidian Trash Explorer&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1630&quot; height=&quot;1158&quot; srcset=&quot;https://permortensen.com/assets/images/dist/BBIRXElP7x-800.png 800w, https://permortensen.com/assets/images/dist/BBIRXElP7x-1200.png 1200w, https://permortensen.com/assets/images/dist/BBIRXElP7x-1630.png 1630w&quot; sizes=&quot;(max-width: 65ch) 100vw, 65ch&quot;&gt;&lt;/picture&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://permortensen.com/projects/obsidian-trash-explorer/&quot;&gt;Read more here&lt;/a&gt;&lt;/p&gt;</content>
  </entry>
  
  <entry>
    <title>Smart Home Automations</title>
    <link href="https://permortensen.com/smart-home-automations/"/>
    <updated>2022-06-11T13:30:47Z</updated>
    <id>https://permortensen.com/smart-home-automations/</id>
    <author>
      <name>Per Mortensen</name>
    </author>
    <content type="html">&lt;p&gt;A few years ago, I bought a Philips Hue starter kit on a whim. Since then, I have replaced all my lightbulbs with Hues and have been getting into the whole smart home thing. Many people see smart home tech as a gimmick, and I don’t blame them: most people who dip their toes into this space buy a Google Home and a couple of smart bulbs, and then leave it at that. You can now ask Google to turn the lights on, and it only takes twice as long as flipping the lightswitch. &lt;em&gt;Truly the home of the future.&lt;/em&gt; No, the real value of connected devices is the ability to integrate them and automate everyday things and have fewer first world problems to deal with. That’s where &lt;a href=&quot;https://www.home-assistant.io/&quot;&gt;Home Assistant&lt;/a&gt; shines – it integrates with almost anything. Here are a couple of things I do with my setup.&lt;/p&gt;&lt;h2&gt;Lights on for multiple people&lt;/h2&gt;&lt;p&gt;Like everyone with smart lights, I have an automation that turns on the light when I come home after dark. Where most automations fail in this area is dealing with more than one person in the home.&lt;/p&gt;&lt;p&gt;My old apartment was fitted with Hue bulbs in every socket, and I had a standard Hue app automation that turned on all the lights when my phone was close to home. One evening, my girlfriend was staying at my place and had gone to bed while I was out. When I arrived a hundred meters from my place, the automation helpfully turned on all the lights and jolted her awake. Imagine being alone and half-asleep when all your lights come on for no apparent reason. That would certainly make me uneasy.&lt;/p&gt;&lt;p&gt;I didn’t want to repeat that, so in Home Assistant, I have an automation that only turns on the lights if no one is already at home. &lt;a href=&quot;https://www.techhive.com/article/579461/revamped-philips-hue-app-gets-streamlined-automations-multi-user-geofencing.html&quot;&gt;This feature has since been added to the Hue app&lt;/a&gt;, but I automate more than just lights: my Netatmo radiator valves also lowers their target temperature when everyone leaves. Also, the lights only come on if it’s actually dark out.&lt;/p&gt;&lt;p&gt;In previous versions of Home Assistant, detecting if someone was already home was a bit convoluted. You’d have to create person groups in YAML and rely on a quirk where the &lt;em&gt;trigger&lt;/em&gt; of the automation detected a state change before a &lt;em&gt;condition&lt;/em&gt; would reflect it, but in &lt;a href=&quot;https://www.home-assistant.io/blog/2022/04/06/release-20224/#zones-now-have-a-state&quot;&gt;version 2022.4&lt;/a&gt;, it’s as simple as triggering on a zone state change from 0 to 1 and vice versa.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;trigger:
  - platform: state
    entity_id: zone.home
    from: &amp;quot;0&amp;quot;
    to: &amp;quot;1&amp;quot;
condition:
  - condition: or
    conditions:
      - condition: sun
        before: sunrise
      - condition: sun
        after: sunset
&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;Electricity price monitor&lt;/h2&gt;&lt;p&gt;A few months ago, I made a forecast graph of the hour-by-hour electricity price with data pulled from &lt;a href=&quot;https://www.nordpoolgroup.com/en/Market-data1/&quot;&gt;Nord Pool&lt;/a&gt;, which lets me schedule power-hungry tasks for when it’s cheaper. Now I’m considering ways to collect long-term usage stats to see if we’re actually getting anything out of it. The other day I showed the graph to my friends, who were… well, let’s just say they weren’t as excited about it as me. This weird fascination is something I must have inherited from my dad; he had a habit of tracking this kind of stuff in excruciating detail. I used to think it was pretty lame, but after he passed away last year, I take comfort in the fact that I inherited some of his lameness.&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://permortensen.com/assets/images/dist/Qn5bwWCXkQ-775.png&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://permortensen.com/assets/images/dist/Qn5bwWCXkQ-775.webp 775w&quot; sizes=&quot;(max-width: 65ch) 100vw, 65ch&quot;&gt;&lt;img src=&quot;https://permortensen.com/assets/images/dist/Qn5bwWCXkQ-775.png&quot; alt=&quot;Electricity price graph&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;775&quot; height=&quot;397&quot;&gt;&lt;/picture&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;In Home Assistant, I have embedded the graph on a dashboard running on an ancient 7 inch tablet that sits in my closet. The thing is actually so old it can’t run the &lt;a href=&quot;https://play.google.com/store/apps/details?id=io.homeassistant.companion.android&quot;&gt;Home Assistant app&lt;/a&gt;, but it can open the web interface in Chrome, which is &lt;em&gt;good enough&lt;/em&gt;. The graph I use is hosted &lt;a href=&quot;https://elspot.per.computer/&quot;&gt;here&lt;/a&gt;, and the source code is available &lt;a href=&quot;https://github.com/proog/elspot&quot;&gt;on GitHub&lt;/a&gt;. Note that it’s currently hardcoded to pull prices for the DK2 region (eastern Denmark).&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://permortensen.com/assets/images/dist/5gx-sL04gw-1440.jpeg&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://permortensen.com/assets/images/dist/5gx-sL04gw-800.webp 800w, https://permortensen.com/assets/images/dist/5gx-sL04gw-1200.webp 1200w, https://permortensen.com/assets/images/dist/5gx-sL04gw-1440.webp 1440w&quot; sizes=&quot;(max-width: 65ch) 100vw, 65ch&quot;&gt;&lt;img src=&quot;https://permortensen.com/assets/images/dist/5gx-sL04gw-800.jpeg&quot; alt=&quot;Ancient tablet with Home Assistant Dashboard&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1440&quot; height=&quot;1920&quot; srcset=&quot;https://permortensen.com/assets/images/dist/5gx-sL04gw-800.jpeg 800w, https://permortensen.com/assets/images/dist/5gx-sL04gw-1200.jpeg 1200w, https://permortensen.com/assets/images/dist/5gx-sL04gw-1440.jpeg 1440w&quot; sizes=&quot;(max-width: 65ch) 100vw, 65ch&quot;&gt;&lt;/picture&gt;&lt;/a&gt;&lt;/p&gt;&lt;h2&gt;E-bike battery charging&lt;/h2&gt;&lt;p&gt;I have been riding bikes my whole life and I enjoy it. My last few jobs have given me a longer commute than I’m willing to make on a normal bike, so I have one with an electric motor. I use it daily for work, but the battery can only do one roundtrip on a single charge. Previously, I would charge it as soon as I got home, but as long as it’s ready in the morning, I don’t care when. Since power is usually cheaper at night, I’ve set up an automation that turns on the charger with a &lt;a href=&quot;https://shelly.cloud/products/shelly-plug-s-smart-home-automation-device/&quot;&gt;Shelly smart plug&lt;/a&gt; at 1 AM, and turns it off again when it’s done. On my tablet dashboard, it looks like this:&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://permortensen.com/assets/images/dist/GbdD4F3Dwb-1262.png&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://permortensen.com/assets/images/dist/GbdD4F3Dwb-800.webp 800w, https://permortensen.com/assets/images/dist/GbdD4F3Dwb-1200.webp 1200w, https://permortensen.com/assets/images/dist/GbdD4F3Dwb-1262.webp 1262w&quot; sizes=&quot;(max-width: 65ch) 100vw, 65ch&quot;&gt;&lt;img src=&quot;https://permortensen.com/assets/images/dist/GbdD4F3Dwb-800.png&quot; alt=&quot;Battery charging automation entities&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1262&quot; height=&quot;365&quot; srcset=&quot;https://permortensen.com/assets/images/dist/GbdD4F3Dwb-800.png 800w, https://permortensen.com/assets/images/dist/GbdD4F3Dwb-1200.png 1200w, https://permortensen.com/assets/images/dist/GbdD4F3Dwb-1262.png 1262w&quot; sizes=&quot;(max-width: 65ch) 100vw, 65ch&quot;&gt;&lt;/picture&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;The automation depends on a software toggle, as I don’t usually use the bike on weekends or when it’s raining. To avoid turning on the charger for no reason, the automation itself turns off the toggle after it’s done. At first, I had set it up so I had to manually enable it every day when I got home. One day I forgot to do that, and I was met with no juice the next morning. Something had to be done.&lt;/p&gt;&lt;p&gt;Ideally, the system would detect if my bike had left (and later returned) and flip the toggle. I had already duct taped an AirTag to it (very stylish), but AirTags can’t yet be tracked by Home Assistant. As an alternative, I set up a &lt;a href=&quot;https://www.home-assistant.io/integrations/zone/&quot;&gt;passive zone&lt;/a&gt; at a point on my commute that I don’t otherwise visit, and flip the toggle if my phone enters it. That means that when I’m biking to work, the charger is automatically primed for charging, but if I take the train or work from home, the charger stays off. Some days, I bike to work, take the train home, and only bring the bike home the day after. To account for that, the zone only triggers in the afternoon.&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://permortensen.com/assets/images/dist/DWZ3hD6pB8-428.png&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://permortensen.com/assets/images/dist/DWZ3hD6pB8-428.webp 428w&quot; sizes=&quot;(max-width: 65ch) 100vw, 65ch&quot;&gt;&lt;img src=&quot;https://permortensen.com/assets/images/dist/DWZ3hD6pB8-428.png&quot; alt=&quot;Passive zone on a map&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;428&quot; height=&quot;270&quot;&gt;&lt;/picture&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;The “optimal charging start time” entity in the screenshot is an additional automation I’m working on that will eventually plan the best charging time by itself. It works like this:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;Get the electricity price forecast&lt;/li&gt;&lt;li&gt;Find the cheapest 4 hour timespan that ends before 6 AM (when I usually wake up)&lt;/li&gt;&lt;li&gt;Schedule the battery charger automation to run at that time&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;I’m just testing it out for now, which is why it’s just shown on the dashboard. The logic is exposed as a serverless function &lt;a href=&quot;https://github.com/proog/elspot/blob/main/functions/cheapest-range.js&quot;&gt;with my graph&lt;/a&gt; and Home Assistant polls it like this:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;rest:
  - scan_interval: 3600 # 1 hour
    resource: &amp;quot;https://elspot.per.computer/cheapest-range&amp;quot;
    params:
      hours: 4
      maxEndTime: &amp;gt;
        {{ (today_at(&amp;quot;06:00&amp;quot;) if now() &amp;lt; today_at(&amp;quot;02:00&amp;quot;) else today_at(&amp;quot;06:00&amp;quot;) + timedelta(days=1)).isoformat() }}
    sensor:
      - name: &amp;quot;Optimal charging start time&amp;quot;
        value_template: &amp;quot;{{ as_local(as_datetime(value_json.startTime)).time() }}&amp;quot;
&lt;/code&gt;&lt;/pre&gt;</content>
  </entry>
  
  <entry>
    <title>VS Code keymap for Obsidian</title>
    <link href="https://permortensen.com/vs-code-keymap-for-obsidian/"/>
    <updated>2021-09-24T17:43:00Z</updated>
    <id>https://permortensen.com/vs-code-keymap-for-obsidian/</id>
    <author>
      <name>Per Mortensen</name>
    </author>
    <content type="html">&lt;p&gt;I recently got the chance to try out &lt;a href=&quot;https://obsidian.md&quot;&gt;Obsidian&lt;/a&gt;, and while it’s still in dire need of some UX polish, it strikes me as a very promising project that aligns well with my personal philosophy of what a notes app should do. Perhaps one day it will replace &lt;a href=&quot;https://bear.app/&quot;&gt;Bear&lt;/a&gt; as my favorite notes app.&lt;/p&gt;&lt;p&gt;While the editor looks superficially like the one in Visual Studio Code it differs quite a bit in its default keyboard shortcuts, which is annoying if you use both apps regularly. Obsidian’s shortcut system is not quite as sophisticated as Code’s (for instance, key sequences are not supported) but I’ve done what I can to make Obsidian behave more Code-like. In particular, remapping the quick switcher and command palette to &lt;code&gt;Cmd+P&lt;/code&gt; and &lt;code&gt;Cmd+Shift+P&lt;/code&gt;, respectively, is a small change that makes a big difference.&lt;/p&gt;&lt;p&gt;Here’s my current Obsidian keymap customizations. You can copy some or all of them into &lt;code&gt;.obsidian/hotkeys.json&lt;/code&gt; inside each vault.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &amp;quot;editor:swap-line-down&amp;quot;: [
    {
      &amp;quot;modifiers&amp;quot;: [&amp;quot;Alt&amp;quot;],
      &amp;quot;key&amp;quot;: &amp;quot;ArrowDown&amp;quot;
    }
  ],
  &amp;quot;editor:swap-line-up&amp;quot;: [
    {
      &amp;quot;modifiers&amp;quot;: [&amp;quot;Alt&amp;quot;],
      &amp;quot;key&amp;quot;: &amp;quot;ArrowUp&amp;quot;
    }
  ],
  &amp;quot;command-palette:open&amp;quot;: [
    {
      &amp;quot;modifiers&amp;quot;: [&amp;quot;Mod&amp;quot;, &amp;quot;Shift&amp;quot;],
      &amp;quot;key&amp;quot;: &amp;quot;P&amp;quot;
    }
  ],
  &amp;quot;switcher:open&amp;quot;: [
    {
      &amp;quot;modifiers&amp;quot;: [&amp;quot;Mod&amp;quot;],
      &amp;quot;key&amp;quot;: &amp;quot;P&amp;quot;
    }
  ],
  &amp;quot;editor:delete-paragraph&amp;quot;: [
    {
      &amp;quot;modifiers&amp;quot;: [&amp;quot;Mod&amp;quot;, &amp;quot;Shift&amp;quot;],
      &amp;quot;key&amp;quot;: &amp;quot;K&amp;quot;
    }
  ],
  &amp;quot;workspace:close-others&amp;quot;: [
    {
      &amp;quot;modifiers&amp;quot;: [&amp;quot;Alt&amp;quot;, &amp;quot;Mod&amp;quot;],
      &amp;quot;key&amp;quot;: &amp;quot;T&amp;quot;
    }
  ],
  &amp;quot;workspace:copy-path&amp;quot;: [
    {
      &amp;quot;modifiers&amp;quot;: [&amp;quot;Alt&amp;quot;, &amp;quot;Mod&amp;quot;],
      &amp;quot;key&amp;quot;: &amp;quot;C&amp;quot;
    }
  ],
  &amp;quot;file-explorer:open&amp;quot;: [
    {
      &amp;quot;modifiers&amp;quot;: [&amp;quot;Mod&amp;quot;, &amp;quot;Shift&amp;quot;],
      &amp;quot;key&amp;quot;: &amp;quot;E&amp;quot;
    }
  ],
  &amp;quot;editor:toggle-bold&amp;quot;: [
    {
      &amp;quot;modifiers&amp;quot;: [&amp;quot;Mod&amp;quot;, &amp;quot;Shift&amp;quot;],
      &amp;quot;key&amp;quot;: &amp;quot;B&amp;quot;
    }
  ],
  &amp;quot;app:toggle-left-sidebar&amp;quot;: [
    {
      &amp;quot;modifiers&amp;quot;: [&amp;quot;Mod&amp;quot;],
      &amp;quot;key&amp;quot;: &amp;quot;B&amp;quot;
    }
  ],
  &amp;quot;editor:focus-left&amp;quot;: [
    {
      &amp;quot;modifiers&amp;quot;: [&amp;quot;Mod&amp;quot;],
      &amp;quot;key&amp;quot;: &amp;quot;1&amp;quot;
    }
  ],
  &amp;quot;editor:focus-right&amp;quot;: [
    {
      &amp;quot;modifiers&amp;quot;: [&amp;quot;Mod&amp;quot;],
      &amp;quot;key&amp;quot;: &amp;quot;2&amp;quot;
    }
  ],
  &amp;quot;app:go-back&amp;quot;: [
    {
      &amp;quot;modifiers&amp;quot;: [&amp;quot;Ctrl&amp;quot;],
      &amp;quot;key&amp;quot;: &amp;quot;-&amp;quot;
    }
  ],
  &amp;quot;app:go-forward&amp;quot;: [
    {
      &amp;quot;modifiers&amp;quot;: [&amp;quot;Ctrl&amp;quot;, &amp;quot;Shift&amp;quot;],
      &amp;quot;key&amp;quot;: &amp;quot;-&amp;quot;
    }
  ]
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;a href=&quot;https://gist.github.com/proog/b0046014507dd717e8c2bb490ccde6bc&quot;&gt;hotkeys.json on GitHub&lt;/a&gt;&lt;/p&gt;</content>
  </entry>
  
  <entry>
    <title>Automating Bear notes backup</title>
    <link href="https://permortensen.com/automating-bear-notes-backup/"/>
    <updated>2021-06-05T16:15:00Z</updated>
    <id>https://permortensen.com/automating-bear-notes-backup/</id>
    <author>
      <name>Per Mortensen</name>
    </author>
    <content type="html">&lt;p&gt;All note apps suck in some way or another. Apple Notes is too basic, Notion is too slow, Obsidian is too intimidating. For me, &lt;a href=&quot;https://bear.app/&quot;&gt;Bear&lt;/a&gt; currently strikes a nice balance between usability (well designed apps) and openness (first-class Markdown support, easy export). Bear is not perfect; it’s Apple-only, and the editor lacks some Markdown features, but I use it daily and like it a lot (and they’re apparently working on &lt;a href=&quot;https://bear.app/faq/What%20about%20Bear%20for%20web,%20Android,%20&amp;amp;%20Windows/&quot;&gt;both&lt;/a&gt; &lt;a href=&quot;https://bear.app/alpha/&quot;&gt;issues&lt;/a&gt;).&lt;/p&gt;&lt;p&gt;As my collection of notes grows, it should naturally become part of my backup process, which consists of a homegrown shell script that backs up a number of things, including databases, cloud storage and personal files. While Bear has a handy backup/restore feature in both the macOS and iOS app, it has no CLI I can use from my script. The only supported way to make a backup is through its GUI.&lt;/p&gt;&lt;p&gt;Sure, you could easily make a copy of the underlying SQLite database, but there’s no guarantee Bear can open it a year down the line after updates have migrated the database schema, and it’s pretty unintuitive to work with directly should Bear no longer be around. Bear’s own backups, meanwhile, are just zips of &lt;a href=&quot;http://textbundle.org/&quot;&gt;TextBundle&lt;/a&gt; files, which makes recovery easy both in- and outside of Bear.&lt;/p&gt;&lt;p&gt;So here’s an AppleScript for macOS that automates the clicks and keystrokes needed to back up notes through Bear. It can be run directly from a terminal or shell script and waits around for the backup process to complete, so the file will be ready when the script exits. The whole thing does rely on some pretty shaky assumptions about the Bear interface, but GUI automation is, as always, notoriously brittle.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Important:&lt;/strong&gt; Make sure the backup directory exists. It will not be created for you.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-applescript&quot;&gt;#!/usr/bin/osascript

set backupPath to &amp;quot;~/Desktop/&amp;quot;
set osVersion to {system attribute &amp;quot;sys1&amp;quot;, system attribute &amp;quot;sys2&amp;quot;, system attribute &amp;quot;sys3&amp;quot;}

-- Launch/switch to Bear
set previousFrontmostApp to (path to frontmost application) as text
activate application &amp;quot;Bear&amp;quot;
repeat until application &amp;quot;Bear&amp;quot; is running
    delay 0.5
end repeat
delay 0.5

tell application &amp;quot;System Events&amp;quot;
    tell process &amp;quot;Bear&amp;quot;
        click menu item &amp;quot;Backup Notes...&amp;quot; of menu &amp;quot;File&amp;quot; of menu bar 1

        -- Wait for the file dialog to appear
        repeat until window &amp;quot;Please choose the location where the notes will be saved&amp;quot; exists
            delay 0.5
        end repeat
        delay 0.5

        tell window &amp;quot;Please choose the location where the notes will be saved&amp;quot;
            -- Use the Cmd+Shift+G &amp;quot;go to folder&amp;quot; shortcut
            keystroke &amp;quot;g&amp;quot; using {command down, shift down}

            -- Wait for the &amp;quot;go to folder&amp;quot; window to appear
            repeat until sheet 1 exists
                delay 0.5
            end repeat
            delay 0.5

            -- Navigate to the backup folder
            tell sheet 1
                if item 1 of osVersion &amp;lt; 12 then
                    -- Classic &amp;quot;go to folder&amp;quot; dialog
                    set value of combo box 1 to backupPath
                    click button &amp;quot;Go&amp;quot;
                else
                    -- Monterey updated &amp;quot;go to folder&amp;quot; to a Spotlight-esque command palette
                    set value of text field 1 to backupPath
                    key code 36
                end if
            end tell

            -- Wait for the &amp;quot;go to folder&amp;quot; window to close
            repeat while sheet 1 exists
                delay 0.5
            end repeat
            delay 0.5

            click button &amp;quot;Export notes&amp;quot;
        end tell

        -- Wait for the untitled backup progress window to appear, then close
        repeat until window &amp;quot;Window&amp;quot; exists
            delay 0.5
        end repeat
        repeat while window &amp;quot;Window&amp;quot; exists
            delay 0.5
        end repeat
    end tell
end tell

-- Switch back to previously activated app
activate application previousFrontmostApp
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;a href=&quot;https://gist.github.com/proog/989c93cc546afc507a95334546362ab6&quot;&gt;backup-bear-notes.applescript on GitHub&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&lt;em&gt;Update 2022-03-14: Updated script to be Monterey-compatible.&lt;/em&gt;&lt;/p&gt;</content>
  </entry>
  
  <entry>
    <title>Official treats for official beasts</title>
    <link href="https://permortensen.com/official-treats-for-official-beasts/"/>
    <updated>2021-02-12T17:36:00Z</updated>
    <id>https://permortensen.com/official-treats-for-official-beasts/</id>
    <author>
      <name>Per Mortensen</name>
    </author>
    <content type="html">&lt;p&gt;Last year, &lt;a href=&quot;https://permortensen.com/a-treat-for-beasts/&quot;&gt;I wrote about my obsession with treatsforbeasts&lt;/a&gt; and especially its weird music.&lt;/p&gt;&lt;p&gt;Well, a new album just dropped, and it’s a compilation of the original music used in every major treatsforbeasts video, including &lt;em&gt;I Love the Sound of Screaming Babies&lt;/em&gt; and the popular &lt;em&gt;Beasts&lt;/em&gt;. This makes my unofficial upload of the former obsolete, so I’ve deleted it from the post.&lt;/p&gt;&lt;p&gt;Besides the magnificent Roii song, I’m particularly interested in what the album calls &lt;em&gt;Daddy’s a Freak&lt;/em&gt;, an instrumental track used in the 2017 &lt;em&gt;TreatsforBeasts&lt;/em&gt; video that’s partly a both ominous and hopeful chant and partly a slow piano rendition of the climactic synth riff from the older &lt;em&gt;beasts&lt;/em&gt; video.&lt;/p&gt;&lt;p&gt;As I don’t have anything else to add, I guess this is little more than an unpaid advert for the album. &lt;a href=&quot;https://sanguinarius.bandcamp.com/album/treatsforbeasts-the-music&quot;&gt;Go listen to it&lt;/a&gt; if you’re into that kind of thing.&lt;/p&gt;</content>
  </entry>
  
  <entry>
    <title>Tracking down an obscure album</title>
    <link href="https://permortensen.com/tracking-down-an-obscure-album/"/>
    <updated>2020-09-08T18:48:00Z</updated>
    <id>https://permortensen.com/tracking-down-an-obscure-album/</id>
    <author>
      <name>Per Mortensen</name>
    </author>
    <content type="html">&lt;p&gt;Sometime in 2016 I was playing &lt;em&gt;Elite: Dangerous&lt;/em&gt; and listening to &lt;a href=&quot;https://www.radiosidewinder.com/&quot;&gt;Radio Sidewinder&lt;/a&gt; when a gorgeous track came on. Turned out this track was &lt;em&gt;Childhood&lt;/em&gt; by Belgian trip hop group Airlock, and I wanted to listen to it again. Usually, I’d either find it in a torrent, on some shady Russian mp3 sharing site, or maybe even buy it with actual money like a decent person. With this track, and the album it’s on, it’s not quite that easy.&lt;/p&gt;&lt;p&gt;Airlock was active in the early-mid 2000’s and released two studio albums around this time — &lt;em&gt;Drystar&lt;/em&gt; from 2002 and &lt;em&gt;Symptomatic&lt;/em&gt; from 2004 — as well as a soundtrack album for a movie called &lt;em&gt;The Room&lt;/em&gt; (no, not &lt;a href=&quot;http://www.imdb.com/title/tt0368226/&quot;&gt;the famous one&lt;/a&gt;, it’s actually &lt;a href=&quot;http://www.imdb.com/title/tt0808450/&quot;&gt;this one&lt;/a&gt; that you haven’t heard of). These albums are widely available, but none of them contain &lt;em&gt;Childhood&lt;/em&gt;. That track happens to be from a fourth release from 2005: the soundtrack for the documentary &lt;em&gt;The Long Journey Home&lt;/em&gt;. It’s listed on the band’s extremely stubby &lt;a href=&quot;https://en.wikipedia.org/wiki/Airlock_(band)&quot;&gt;Wikipedia article&lt;/a&gt;, but &lt;a href=&quot;https://musicbrainz.org/artist/4e3e072f-452a-4bc0-a993-2fe3d456e908/releases&quot;&gt;MusicBrainz doesn’t know about it&lt;/a&gt;, and it’s also not listed on any store page, torrent site, or low quality MP3 site I can find. What.cd, back when it was still in operation, didn’t have or mention it. &lt;a href=&quot;http://www.airlock.be/&quot;&gt;Airlock’s own website&lt;/a&gt;, when I researched it in 2016, had last been updated in 2004 and so predated the album (and is now defunct). It’s pretty obscure.&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://permortensen.com/assets/images/dist/psJlB4WwO8-300.png&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://permortensen.com/assets/images/dist/psJlB4WwO8-300.webp 300w&quot; sizes=&quot;(max-width: 65ch) 100vw, 65ch&quot;&gt;&lt;img src=&quot;https://permortensen.com/assets/images/dist/psJlB4WwO8-300.png&quot; alt=&quot;The Long Journey Home cover art&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;300&quot; height=&quot;270&quot;&gt;&lt;/picture&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;It does have a couple of references around the web, though. &lt;a href=&quot;https://www.discogs.com/Airlock-The-Long-Journey-Home/release/7977102&quot;&gt;Discogs lists it&lt;/a&gt;, and trip-hop.net, a French music review site, contains &lt;a href=&quot;http://www.trip-hop.net/album-1109-airlock-the-long-journey-home-soundtrack-miramax-films.html&quot;&gt;a 2005 article about it&lt;/a&gt;. I couldn’t read French to save my life, but thanks to some dodgy machine translation the article gives us this juicy bit of info:&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;Just a few months after the release of “Symptomatic” which had confirmed the group as an ascending value of Belgian trip-hop, a press release from the group informs us of a new release, which is in fact film music!&lt;br&gt;. . .&lt;br&gt;Good luck finding this album, however, because it is not scheduled for sale … but can be won on Trip-Hop.Net thanks to Airlock which keeps us some for you! A rare and very precious collector …&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;So it looks like &lt;em&gt;The Long Journey Home&lt;/em&gt; is indeed not available anywhere except in a giveaway on a French music site… 15 years ago. Despite the “nostalgic” look of trip-hop.net, the site is still in operation, so I contacted trip-hop management, who confirmed it.&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;Hey,&lt;br&gt;apparently the album wasn’t released, but available in very few copies and Airlock offered some via a contest on trip-hop.net.&lt;br&gt;I can’t share the name of the winners, but I’m trying to get some infos among the oldest members of our redaction. Maybe they have some tips to find a copy.&lt;br&gt;best regards from France !&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;After this exchange, months went by, and they never contacted me again. I contacted Radio Sidewinder’s admin and their music provider Radionomy. I contacted Airlock member Ernst Meinrath, who in 2012 uploaded &lt;a href=&quot;https://www.youtube.com/watch?v=3P1ZCcO7_ao&quot;&gt;a trailer for the documentary&lt;/a&gt; that actually starts out playing &lt;em&gt;Childhood&lt;/em&gt;. No success.&lt;/p&gt;&lt;p&gt;But it turned out the answer had been right in front of me the whole time. I realized the album’s Discogs page was oddly detailed compared to every other mention of its existence, and a single user did claim to own it. This one owner turned out to have found it on eBay after searching for it for years, and he was kind enough to send me the audio.&lt;/p&gt;&lt;p&gt;This all took place in 2016, and I’ve had a draft about it lying around since, mostly because I didn’t know how to end it. It felt wrong to just go and share the album, which is to my knowledge still quite rare. But without something to show for all my effort, the story just felt extremely unsatisfying. After a good 4 year ponder I’ve now decided to at least share the track that started it all. &lt;a href=&quot;https://static.per.computer/Airlock%20-%20Childhood.mp3&quot;&gt;Here’s &lt;em&gt;Childhood&lt;/em&gt; by Airlock.&lt;/a&gt;&lt;/p&gt;</content>
  </entry>
  
  <entry>
    <title>A Treat for Beasts</title>
    <link href="https://permortensen.com/a-treat-for-beasts/"/>
    <updated>2020-03-25T10:32:00Z</updated>
    <id>https://permortensen.com/a-treat-for-beasts/</id>
    <author>
      <name>Per Mortensen</name>
    </author>
    <content type="html">&lt;p&gt;The most annoying thing about watching the amount of YouTube that I do is that I’m into a bunch of weird creators with no one to discuss them with. One of them is &lt;a href=&quot;https://www.youtube.com/user/treatsforbeasts&quot;&gt;treatsforbeasts&lt;/a&gt;, an enigmatic creator dabbling in the intersection of music, comedy, and the macabre.&lt;/p&gt;&lt;p&gt;Treatsforbeasts’ productions are generally unpredictable, with their last 5 videos uploaded years apart and featuring a mix of cartoons, original music, and comedy skits. My current favorite has to be the 2017 video &lt;a href=&quot;https://www.youtube.com/watch?v=XGB-WoddGuU&quot;&gt;TreatsforBeasts&lt;/a&gt; because of its impressive diversity and production value. While the most recent video, &lt;a href=&quot;https://www.youtube.com/watch?v=RSS9krxCD_4&quot;&gt;The Beast Is Dead&lt;/a&gt;, is similar in style, dedicating half its runtime to an extended skit of questionable quality brings it down somewhat. However, just before the halfway point, there’s &lt;a href=&quot;https://youtu.be/RSS9krxCD_4?t=696&quot;&gt;a 4 minute long music video&lt;/a&gt; for the song &lt;em&gt;I Love the Sound of Screaming Babies&lt;/em&gt; by a fictional artist called Roii. The video works well as a humorous parody, but the song itself is legitimately a banger, and in my COVID-19 isolation I’ve caught myself singing it way too often.&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://permortensen.com/assets/images/dist/-81CB5qoip-1108.jpeg&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://permortensen.com/assets/images/dist/-81CB5qoip-800.webp 800w, https://permortensen.com/assets/images/dist/-81CB5qoip-1108.webp 1108w&quot; sizes=&quot;(max-width: 65ch) 100vw, 65ch&quot;&gt;&lt;img src=&quot;https://permortensen.com/assets/images/dist/-81CB5qoip-800.jpeg&quot; alt=&quot;Roii album ad&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1108&quot; height=&quot;816&quot; srcset=&quot;https://permortensen.com/assets/images/dist/-81CB5qoip-800.jpeg 800w, https://permortensen.com/assets/images/dist/-81CB5qoip-1108.jpeg 1108w&quot; sizes=&quot;(max-width: 65ch) 100vw, 65ch&quot;&gt;&lt;/picture&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;To my huge disappointment, &lt;em&gt;I Love the Sound of Screaming Babies&lt;/em&gt; by Roii is nowhere to be found outside treatsforbeasts’ video. At least not online; I’m currently unable to check petrol stations in Slovania. But I can’t help it: I really want to listen to Roii’s beautiful voice on the go. To that end, I’ve extracted an mp3 version myself:&lt;/p&gt;&lt;p&gt;&lt;em&gt;(Edit 2021-02-12: &lt;a href=&quot;https://permortensen.com/official-treats-for-official-beasts/&quot;&gt;There’s now an official version out&lt;/a&gt;, so the link has been removed)&lt;/em&gt;&lt;/p&gt;&lt;p&gt;This is obviously copyright infringement, but I hope treatsforbeasts will be alright with it. Being sourced from a YouTube video, the audio quality is not ideal, and I hope treatsforbeasts will one day release an official, high quality version.&lt;/p&gt;</content>
  </entry>
  
  <entry>
    <title>whatever.sh</title>
    <link href="https://permortensen.com/whatever-sh/"/>
    <updated>2020-03-14T11:31:00Z</updated>
    <id>https://permortensen.com/whatever-sh/</id>
    <author>
      <name>Per Mortensen</name>
    </author>
    <content type="html">&lt;p&gt;I made a tiny new project!&lt;/p&gt;&lt;p&gt;We have upwards of 30 code repositories at work, and I consistently run the same commands every time I return to each of them. First I do a &lt;code&gt;git fetch&lt;/code&gt; so I know what branches people are working on, then I do a &lt;code&gt;git pull&lt;/code&gt; to make sure my local branch is up to date. Then I usually do a &lt;code&gt;dotnet restore&lt;/code&gt; (we primarily do .NET at work), and only then am I ready to start doing whatever I came in there to do.&lt;/p&gt;&lt;p&gt;I do this with my collaborative hobby projects too, and now that GitHub makes automated pull requests for security updates, even my private repositories aren’t safe from going out-of-date. And while I do .NET at work, my personal repos are much more diverse, requiring a bunch of different tools to make them usable.&lt;/p&gt;&lt;p&gt;So I made a script to automatically get the repo into a usable state: &lt;a href=&quot;https://github.com/proog/whatever&quot;&gt;whatever&lt;/a&gt;. It’s called that because that’s how I feel about running these housekeeping commands: &lt;em&gt;just do whatever, man.&lt;/em&gt;&lt;/p&gt;&lt;p&gt;I would really like to add more features to it, but since it’s based on my own usage patterns, I’ll have to keep an eye out for stuff I still end up doing after &lt;code&gt;whatever&lt;/code&gt;.&lt;/p&gt;</content>
  </entry>
  
  <entry>
    <title>Let&#39;s fix FFXII&#39;s save UI</title>
    <link href="https://permortensen.com/lets-fix-ffxiis-save-ui/"/>
    <updated>2019-11-22T17:30:00Z</updated>
    <id>https://permortensen.com/lets-fix-ffxiis-save-ui/</id>
    <author>
      <name>Per Mortensen</name>
    </author>
    <content type="html">&lt;p&gt;I don’t know much about good user interface design. I’m not quite &lt;a href=&quot;https://userinyerface.com/&quot;&gt;User Inyerface&lt;/a&gt; clueless, though I’m probably not far off either. But stick with me here, because what follows is an example of bad UI that should be obvious enough that anyone would say &lt;em&gt;“yeah, that’s some bad UI.”&lt;/em&gt;&lt;/p&gt;&lt;p&gt;Let me start by saying that I absolutely adore &lt;em&gt;Final Fantasy XII&lt;/em&gt;. I’ve only played through it 1.5 times, but I would defend it to death just the same. It breaks enough established (and bad) Final Fantasy-isms that it stands out from the rest. It has the least annoying battle system, the most intriguing world, and even the most beautiful music of the entire series. Yes, the awkward FF dialogue is still there, but for once it actually seems intentional. I firmly believe it to be the best Final Fantasy game ever made.&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://permortensen.com/assets/images/dist/hpjyJijpzv-1527.jpeg&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://permortensen.com/assets/images/dist/hpjyJijpzv-800.webp 800w, https://permortensen.com/assets/images/dist/hpjyJijpzv-1200.webp 1200w, https://permortensen.com/assets/images/dist/hpjyJijpzv-1527.webp 1527w&quot; sizes=&quot;(max-width: 65ch) 100vw, 65ch&quot;&gt;&lt;img src=&quot;https://permortensen.com/assets/images/dist/hpjyJijpzv-800.jpeg&quot; alt=&quot;FFXII splash&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1527&quot; height=&quot;770&quot; srcset=&quot;https://permortensen.com/assets/images/dist/hpjyJijpzv-800.jpeg 800w, https://permortensen.com/assets/images/dist/hpjyJijpzv-1200.jpeg 1200w, https://permortensen.com/assets/images/dist/hpjyJijpzv-1527.jpeg 1527w&quot; sizes=&quot;(max-width: 65ch) 100vw, 65ch&quot;&gt;&lt;/picture&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;The 2017 PS4 remaster &lt;em&gt;The Zodiac Age&lt;/em&gt; takes the best game in the series and improves on it. But one thing about it that bugs me enough to write the upcoming diatribe is the way it handles loading and saving. It sounds petty, I know, but considering how often you’re likely to save in this game (and load, if you’re the perfectionist type), it’s a tiny annoyance that becomes quite significant with time.&lt;/p&gt;&lt;p&gt;In contrast to the original PS2 release, the remaster uses the PS4 system UI for all its load and save menus, so they’re inherently snappy and readable. These menus don’t gel with the in-game UI, so it’s a bit of an immersion breaker, but it’s not a major issue if the interactions within them are brief.&lt;/p&gt;&lt;p&gt;Let’s start by taking a look at how loading a game is implemented. When you start the game, you’re dumped into the title menu, which looks like this:&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://permortensen.com/assets/images/dist/ecreYo8wqa-1920.jpeg&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://permortensen.com/assets/images/dist/ecreYo8wqa-800.webp 800w, https://permortensen.com/assets/images/dist/ecreYo8wqa-1200.webp 1200w, https://permortensen.com/assets/images/dist/ecreYo8wqa-1920.webp 1920w&quot; sizes=&quot;(max-width: 65ch) 100vw, 65ch&quot;&gt;&lt;img src=&quot;https://permortensen.com/assets/images/dist/ecreYo8wqa-800.jpeg&quot; alt=&quot;Title menu&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1920&quot; height=&quot;1080&quot; srcset=&quot;https://permortensen.com/assets/images/dist/ecreYo8wqa-800.jpeg 800w, https://permortensen.com/assets/images/dist/ecreYo8wqa-1200.jpeg 1200w, https://permortensen.com/assets/images/dist/ecreYo8wqa-1920.jpeg 1920w&quot; sizes=&quot;(max-width: 65ch) 100vw, 65ch&quot;&gt;&lt;/picture&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;Choosing load game instantly brings up a list of saves, with the most recent one preselected. Neat! So far this looks pretty good!&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://permortensen.com/assets/images/dist/kxuw9jot74-1920.png&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://permortensen.com/assets/images/dist/kxuw9jot74-800.webp 800w, https://permortensen.com/assets/images/dist/kxuw9jot74-1200.webp 1200w, https://permortensen.com/assets/images/dist/kxuw9jot74-1920.webp 1920w&quot; sizes=&quot;(max-width: 65ch) 100vw, 65ch&quot;&gt;&lt;img src=&quot;https://permortensen.com/assets/images/dist/kxuw9jot74-800.png&quot; alt=&quot;Load game&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1920&quot; height=&quot;1080&quot; srcset=&quot;https://permortensen.com/assets/images/dist/kxuw9jot74-800.png 800w, https://permortensen.com/assets/images/dist/kxuw9jot74-1200.png 1200w, https://permortensen.com/assets/images/dist/kxuw9jot74-1920.png 1920w&quot; sizes=&quot;(max-width: 65ch) 100vw, 65ch&quot;&gt;&lt;/picture&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;But it only goes downhill from here. Choosing a file brings up the following prompt, asking you if you really want to load this save, with &lt;em&gt;No&lt;/em&gt; preselected.&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://permortensen.com/assets/images/dist/1AEP9uDmNm-1920.png&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://permortensen.com/assets/images/dist/1AEP9uDmNm-800.webp 800w, https://permortensen.com/assets/images/dist/1AEP9uDmNm-1200.webp 1200w, https://permortensen.com/assets/images/dist/1AEP9uDmNm-1920.webp 1920w&quot; sizes=&quot;(max-width: 65ch) 100vw, 65ch&quot;&gt;&lt;img src=&quot;https://permortensen.com/assets/images/dist/1AEP9uDmNm-800.png&quot; alt=&quot;Load game?&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1920&quot; height=&quot;1080&quot; srcset=&quot;https://permortensen.com/assets/images/dist/1AEP9uDmNm-800.png 800w, https://permortensen.com/assets/images/dist/1AEP9uDmNm-1200.png 1200w, https://permortensen.com/assets/images/dist/1AEP9uDmNm-1920.png 1920w&quot; sizes=&quot;(max-width: 65ch) 100vw, 65ch&quot;&gt;&lt;/picture&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;Select &lt;em&gt;Yes&lt;/em&gt; and the file is loaded, after which you’re greeted with the following message.&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://permortensen.com/assets/images/dist/48nUt0hOob-1920.png&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://permortensen.com/assets/images/dist/48nUt0hOob-800.webp 800w, https://permortensen.com/assets/images/dist/48nUt0hOob-1200.webp 1200w, https://permortensen.com/assets/images/dist/48nUt0hOob-1920.webp 1920w&quot; sizes=&quot;(max-width: 65ch) 100vw, 65ch&quot;&gt;&lt;img src=&quot;https://permortensen.com/assets/images/dist/48nUt0hOob-800.png&quot; alt=&quot;Loaded&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1920&quot; height=&quot;1080&quot; srcset=&quot;https://permortensen.com/assets/images/dist/48nUt0hOob-800.png 800w, https://permortensen.com/assets/images/dist/48nUt0hOob-1200.png 1200w, https://permortensen.com/assets/images/dist/48nUt0hOob-1920.png 1920w&quot; sizes=&quot;(max-width: 65ch) 100vw, 65ch&quot;&gt;&lt;/picture&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;Past this, you’re finally in the actual game part of the game. Alright.&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://permortensen.com/assets/images/dist/8bc55BQiGo-1920.jpeg&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://permortensen.com/assets/images/dist/8bc55BQiGo-800.webp 800w, https://permortensen.com/assets/images/dist/8bc55BQiGo-1200.webp 1200w, https://permortensen.com/assets/images/dist/8bc55BQiGo-1920.webp 1920w&quot; sizes=&quot;(max-width: 65ch) 100vw, 65ch&quot;&gt;&lt;img src=&quot;https://permortensen.com/assets/images/dist/8bc55BQiGo-800.jpeg&quot; alt=&quot;In-game&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1920&quot; height=&quot;1080&quot; srcset=&quot;https://permortensen.com/assets/images/dist/8bc55BQiGo-800.jpeg 800w, https://permortensen.com/assets/images/dist/8bc55BQiGo-1200.jpeg 1200w, https://permortensen.com/assets/images/dist/8bc55BQiGo-1920.jpeg 1920w&quot; sizes=&quot;(max-width: 65ch) 100vw, 65ch&quot;&gt;&lt;/picture&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;Let’s say that while playing the game, you come across a save crystal and you decide to save your game manually. Now, the remaster does feature autosaving, but if you’re anything like me, you wouldn’t want to rely solely on autosaves for a long RPG like this. As you touch the crystal, you’re asked if you want to save.&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://permortensen.com/assets/images/dist/9ugc28IV1D-1920.jpeg&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://permortensen.com/assets/images/dist/9ugc28IV1D-800.webp 800w, https://permortensen.com/assets/images/dist/9ugc28IV1D-1200.webp 1200w, https://permortensen.com/assets/images/dist/9ugc28IV1D-1920.webp 1920w&quot; sizes=&quot;(max-width: 65ch) 100vw, 65ch&quot;&gt;&lt;img src=&quot;https://permortensen.com/assets/images/dist/9ugc28IV1D-800.jpeg&quot; alt=&quot;Save crystal&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1920&quot; height=&quot;1080&quot; srcset=&quot;https://permortensen.com/assets/images/dist/9ugc28IV1D-800.jpeg 800w, https://permortensen.com/assets/images/dist/9ugc28IV1D-1200.jpeg 1200w, https://permortensen.com/assets/images/dist/9ugc28IV1D-1920.jpeg 1920w&quot; sizes=&quot;(max-width: 65ch) 100vw, 65ch&quot;&gt;&lt;/picture&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;This prompt was part of the original game and has been preserved in the remaster. Choosing &lt;em&gt;Yes&lt;/em&gt; brings up a list of saves with the additional &lt;em&gt;Create new file&lt;/em&gt; option preselected.&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://permortensen.com/assets/images/dist/nlhL3N4NK1-1920.png&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://permortensen.com/assets/images/dist/nlhL3N4NK1-800.webp 800w, https://permortensen.com/assets/images/dist/nlhL3N4NK1-1200.webp 1200w, https://permortensen.com/assets/images/dist/nlhL3N4NK1-1920.webp 1920w&quot; sizes=&quot;(max-width: 65ch) 100vw, 65ch&quot;&gt;&lt;img src=&quot;https://permortensen.com/assets/images/dist/nlhL3N4NK1-800.png&quot; alt=&quot;Save game&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1920&quot; height=&quot;1080&quot; srcset=&quot;https://permortensen.com/assets/images/dist/nlhL3N4NK1-800.png 800w, https://permortensen.com/assets/images/dist/nlhL3N4NK1-1200.png 1200w, https://permortensen.com/assets/images/dist/nlhL3N4NK1-1920.png 1920w&quot; sizes=&quot;(max-width: 65ch) 100vw, 65ch&quot;&gt;&lt;/picture&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;Both creating a new save and overwriting a previous one makes the game ask for confirmation, with &lt;em&gt;No&lt;/em&gt; preselected, presumably to prevent accidental overwrites.&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://permortensen.com/assets/images/dist/011nFE836u-1920.png&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://permortensen.com/assets/images/dist/011nFE836u-800.webp 800w, https://permortensen.com/assets/images/dist/011nFE836u-1200.webp 1200w, https://permortensen.com/assets/images/dist/011nFE836u-1920.webp 1920w&quot; sizes=&quot;(max-width: 65ch) 100vw, 65ch&quot;&gt;&lt;img src=&quot;https://permortensen.com/assets/images/dist/011nFE836u-800.png&quot; alt=&quot;Save game?&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1920&quot; height=&quot;1080&quot; srcset=&quot;https://permortensen.com/assets/images/dist/011nFE836u-800.png 800w, https://permortensen.com/assets/images/dist/011nFE836u-1200.png 1200w, https://permortensen.com/assets/images/dist/011nFE836u-1920.png 1920w&quot; sizes=&quot;(max-width: 65ch) 100vw, 65ch&quot;&gt;&lt;/picture&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;Choosing the &lt;em&gt;Yes&lt;/em&gt; option performs the actual save operation, followed by a message confirming it.&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://permortensen.com/assets/images/dist/7UWgqOJUcb-1920.png&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://permortensen.com/assets/images/dist/7UWgqOJUcb-800.webp 800w, https://permortensen.com/assets/images/dist/7UWgqOJUcb-1200.webp 1200w, https://permortensen.com/assets/images/dist/7UWgqOJUcb-1920.webp 1920w&quot; sizes=&quot;(max-width: 65ch) 100vw, 65ch&quot;&gt;&lt;img src=&quot;https://permortensen.com/assets/images/dist/7UWgqOJUcb-800.png&quot; alt=&quot;Saved&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1920&quot; height=&quot;1080&quot; srcset=&quot;https://permortensen.com/assets/images/dist/7UWgqOJUcb-800.png 800w, https://permortensen.com/assets/images/dist/7UWgqOJUcb-1200.png 1200w, https://permortensen.com/assets/images/dist/7UWgqOJUcb-1920.png 1920w&quot; sizes=&quot;(max-width: 65ch) 100vw, 65ch&quot;&gt;&lt;/picture&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;Skip that and you’re back at… the list of saves.&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://permortensen.com/assets/images/dist/sFJOU5sMOT-1920.png&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://permortensen.com/assets/images/dist/sFJOU5sMOT-800.webp 800w, https://permortensen.com/assets/images/dist/sFJOU5sMOT-1200.webp 1200w, https://permortensen.com/assets/images/dist/sFJOU5sMOT-1920.webp 1920w&quot; sizes=&quot;(max-width: 65ch) 100vw, 65ch&quot;&gt;&lt;img src=&quot;https://permortensen.com/assets/images/dist/sFJOU5sMOT-800.png&quot; alt=&quot;Save game&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1920&quot; height=&quot;1080&quot; srcset=&quot;https://permortensen.com/assets/images/dist/sFJOU5sMOT-800.png 800w, https://permortensen.com/assets/images/dist/sFJOU5sMOT-1200.png 1200w, https://permortensen.com/assets/images/dist/sFJOU5sMOT-1920.png 1920w&quot; sizes=&quot;(max-width: 65ch) 100vw, 65ch&quot;&gt;&lt;/picture&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;You now need to press &lt;em&gt;Back&lt;/em&gt; to exit the menu, and finally you’re free to proceed where you left off.&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://permortensen.com/assets/images/dist/8oO3vRU3hJ-1920.jpeg&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://permortensen.com/assets/images/dist/8oO3vRU3hJ-800.webp 800w, https://permortensen.com/assets/images/dist/8oO3vRU3hJ-1200.webp 1200w, https://permortensen.com/assets/images/dist/8oO3vRU3hJ-1920.webp 1920w&quot; sizes=&quot;(max-width: 65ch) 100vw, 65ch&quot;&gt;&lt;img src=&quot;https://permortensen.com/assets/images/dist/8oO3vRU3hJ-800.jpeg&quot; alt=&quot;In-game&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1920&quot; height=&quot;1080&quot; srcset=&quot;https://permortensen.com/assets/images/dist/8oO3vRU3hJ-800.jpeg 800w, https://permortensen.com/assets/images/dist/8oO3vRU3hJ-1200.jpeg 1200w, https://permortensen.com/assets/images/dist/8oO3vRU3hJ-1920.jpeg 1920w&quot; sizes=&quot;(max-width: 65ch) 100vw, 65ch&quot;&gt;&lt;/picture&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;By now I hope you’re thinking the same thing I am. That’s a lot of unnecessary button presses for what should, ideally, be as quick and easy as possible. Let’s make some optimisations for these scenarios.&lt;/p&gt;&lt;h2&gt;Drop the prompt after selecting a file to load&lt;/h2&gt;&lt;p&gt;It’s only possible to load games from the main menu so there’s no chance of losing any progress by loading at the wrong time. Loading a save doesn’t modify it, and the load times are only a few seconds, so the benefit of this prompt is non-existent.&lt;/p&gt;&lt;h2&gt;Drop the &lt;em&gt;loading complete&lt;/em&gt; message&lt;/h2&gt;&lt;p&gt;The player will generally know that their chosen save has been loaded when the game transitions from load menu to loading progress bar to in-game. Had the load times been much longer, this message might be used to prevent the player walking off during load and coming back to an unpaused game, but that’s really not the case here.&lt;/p&gt;&lt;h2&gt;Don’t ask for confirmation when making new saves&lt;/h2&gt;&lt;p&gt;It’s absolutely a good idea to prompt before making destructive operations like overwriting previous save files, but accidentally creating a new one is both non-destructive and easy to undo.&lt;/p&gt;&lt;h2&gt;Drop the &lt;em&gt;saving complete&lt;/em&gt; message&lt;/h2&gt;&lt;p&gt;There’s a noticeable progress bar when saving which should hint that the operation is in progress, and silently transitioning from a progress bar to what the player was previously doing should imply that it completed successfully. This is perhaps more of a gray area than the &lt;em&gt;loading complete&lt;/em&gt; message, but I still feel it could be dropped without much risk.&lt;/p&gt;&lt;h2&gt;Close the entire menu after saving&lt;/h2&gt;&lt;p&gt;Instead of reopening the list of saves, go back in-game to let the player continue. Going back to the list makes sense in a hierarchical UI system, but it doesn’t really fit the use case here. Interaction with the save menu is generally a one-off every time, so when the operation has completed, most players will have no further use for it but still have to manually close it. Assuming everything went well, the player’s priority after saving would be to either continue playing or quit the game, and the save menu assists with neither.&lt;/p&gt;&lt;p&gt;So there it is. We’ve fixed this tiny part of &lt;em&gt;Final Fantasy XII: The Zodiac Age&lt;/em&gt; and made it much less annoying to use. It gets out of the player’s way and makes them spend as little time as possible in immersion-breaking system menus.&lt;/p&gt;</content>
  </entry>
  
  <entry>
    <title>That other game</title>
    <link href="https://permortensen.com/that-other-game/"/>
    <updated>2019-02-12T00:00:00Z</updated>
    <id>https://permortensen.com/that-other-game/</id>
    <author>
      <name>Per Mortensen</name>
    </author>
    <content type="html">&lt;p&gt;Remember Crimson Wars? &lt;a href=&quot;https://crimsonwars.net/&quot;&gt;I sure do&lt;/a&gt;. But it’s not the only obscure game I remember from my childhood.&lt;/p&gt;&lt;p&gt;When I was in primary school I was really bad at math. I think my parents were worried I would never pick it up if they didn’t help it along, and so we had a couple of educational math games at our house. One of them was &lt;em&gt;Gys på Regneslottet&lt;/em&gt;, released in 1996. It’s a math test disguised as a spooky castle adventure in which you open secret doorways by solving complicated puzzles such as &lt;strong&gt;5 + ? = 6&lt;/strong&gt;.&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://permortensen.com/assets/images/dist/3pgLsR4RzY-640.png&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://permortensen.com/assets/images/dist/3pgLsR4RzY-640.webp 640w&quot; sizes=&quot;(max-width: 65ch) 100vw, 65ch&quot;&gt;&lt;img src=&quot;https://permortensen.com/assets/images/dist/3pgLsR4RzY-640.png&quot; alt=&quot;Gys på Regneslottet screenshot&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;640&quot; height=&quot;400&quot;&gt;&lt;/picture&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;A few months ago, a friend and I somehow ended up talking about it as he also remembered playing it back in the late 90s. Google it, however, and you’ll find even less information about it than Crimson Wars. For the most part, it’s off hand comments in random, decade-old forum posts. Now, more than two decades after its release, I haven’t found anywhere to download or buy it, and the forum users promising to dig it out of their attics eventually fall silent. At first glance, it feels a lot like those “lost game” creepypastas that used to be popular; it just needs some tragic backstory to draw people in.&lt;/p&gt;&lt;p&gt;But of course, that feeling quickly subsides when you realize that two public libraries claim to stock a copy of it. When I looked, &lt;em&gt;Gys på Regneslottet&lt;/em&gt; was currently out on loan, and I do wonder what kindred spirit got the idea to borrow this thing at the same time as me. Recently, their time was up and it got sent to me. What can I say, the system works.&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://permortensen.com/assets/images/dist/YZ4JJBXcfJ-640.png&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://permortensen.com/assets/images/dist/YZ4JJBXcfJ-640.webp 640w&quot; sizes=&quot;(max-width: 65ch) 100vw, 65ch&quot;&gt;&lt;img src=&quot;https://permortensen.com/assets/images/dist/YZ4JJBXcfJ-640.png&quot; alt=&quot;Gys på Regneslottet screenshot&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;640&quot; height=&quot;400&quot;&gt;&lt;/picture&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;As virtually no images of the game or its box exist online, I had hoped to scan whatever physical material came with this copy. Unfortunately, the front and back covers were sloppily cut out of the original box and placed in a soft plastic sleeve. The paper and plastic have fused together and it’s now impossible to remove the paper without tearing it. Bummer. You can find what’s left of it at the end of this post. No manual was included.&lt;/p&gt;&lt;p&gt;With a title like &lt;em&gt;Gys på Regneslottet&lt;/em&gt; I had always assumed the game was some local Danish product, but it seems the game is in fact somewhat international. Gili Soft, who translated and published the Danish version, was a tiny company that &lt;a href=&quot;https://datacvr.virk.dk/data/visenhed?enhedstype=virksomhed&amp;amp;id=19283836&quot;&gt;operated between 1996 and 2002&lt;/a&gt;, but the game was apparently developed by a company called Compedia (&lt;a href=&quot;https://compedia.net/&quot;&gt;probably these guys&lt;/a&gt;). I haven’t been able to determine the original title of the game, but it seems to exist in Turkey as &lt;a href=&quot;http://www.elit-shopping.biz/default.asp?git=9&amp;amp;urun=47&quot;&gt;Sevimli Hayalet&lt;/a&gt;. If you happen to know anything else about the game, do get in touch!&lt;/p&gt;&lt;p&gt;To conclude, &lt;a href=&quot;https://static.per.computer/regneslottet.iso&quot;&gt;here’s my rip of the disc&lt;/a&gt;. The game was made for Windows 9x and won’t run on any sort of modern PC, but here’s a hot tip: the virtual machine for Crimson Wars should work if you decrease the colors to 256. Good luck slaying the dragon!&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://permortensen.com/assets/images/dist/zpT4Jo2JCP-640.png&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://permortensen.com/assets/images/dist/zpT4Jo2JCP-640.webp 640w&quot; sizes=&quot;(max-width: 65ch) 100vw, 65ch&quot;&gt;&lt;img src=&quot;https://permortensen.com/assets/images/dist/zpT4Jo2JCP-640.png&quot; alt=&quot;Gys på Regneslottet screenshot&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;640&quot; height=&quot;400&quot;&gt;&lt;/picture&gt;&lt;/a&gt;&lt;a href=&quot;https://permortensen.com/assets/images/dist/Rqx3dLwW-s-640.png&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://permortensen.com/assets/images/dist/Rqx3dLwW-s-640.webp 640w&quot; sizes=&quot;(max-width: 65ch) 100vw, 65ch&quot;&gt;&lt;img src=&quot;https://permortensen.com/assets/images/dist/Rqx3dLwW-s-640.png&quot; alt=&quot;Gys på Regneslottet screenshot&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;640&quot; height=&quot;400&quot;&gt;&lt;/picture&gt;&lt;/a&gt;&lt;a href=&quot;https://permortensen.com/assets/images/dist/9A-aSDg9at-600.jpeg&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://permortensen.com/assets/images/dist/9A-aSDg9at-600.webp 600w&quot; sizes=&quot;(max-width: 65ch) 100vw, 65ch&quot;&gt;&lt;img src=&quot;https://permortensen.com/assets/images/dist/9A-aSDg9at-600.jpeg&quot; alt=&quot;Gys på Regneslottet front&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;600&quot; height=&quot;840&quot;&gt;&lt;/picture&gt;&lt;/a&gt;&lt;a href=&quot;https://permortensen.com/assets/images/dist/GcfdKmnnbX-600.jpeg&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://permortensen.com/assets/images/dist/GcfdKmnnbX-600.webp 600w&quot; sizes=&quot;(max-width: 65ch) 100vw, 65ch&quot;&gt;&lt;img src=&quot;https://permortensen.com/assets/images/dist/GcfdKmnnbX-600.jpeg&quot; alt=&quot;Gys på Regneslottet back&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;600&quot; height=&quot;835&quot;&gt;&lt;/picture&gt;&lt;/a&gt;&lt;/p&gt;</content>
  </entry>
  
  <entry>
    <title>What are hats?</title>
    <link href="https://permortensen.com/what-are-hats/"/>
    <updated>2018-11-18T00:00:00Z</updated>
    <id>https://permortensen.com/what-are-hats/</id>
    <author>
      <name>Per Mortensen</name>
    </author>
    <content type="html">&lt;p&gt;Well, it is quite simple. Let us take a look.&lt;/p&gt;&lt;object type=&quot;application/x-shockwave-flash&quot; data=&quot;https://static.per.computer/hatshow.swf&quot; width=&quot;550&quot; height=&quot;300&quot;&gt;&lt;param name=&quot;movie&quot; value=&quot;https://static.per.computer/hatshow.swf&quot;&gt;&lt;param name=&quot;quality&quot; value=&quot;high&quot;&gt;&lt;/object&gt;&lt;p&gt;I was never really into &lt;a href=&quot;http://weebls-stuff.com/&quot;&gt;Weebl’s Stuff&lt;/a&gt;. There are a couple of funny (or annoying, depending on your tastes) toons and tunes, but I never watched it on the regular back when it was hot shit a decade ago. But there’s one of his toons that stuck with me since, and thats &lt;em&gt;Weebl and Bob episode 109: hat show&lt;/em&gt;.&lt;/p&gt;&lt;p&gt;It was animated by Weebl’s Stuff contributor Skoo and originally released on June 9, 2006. The other day, a friend of mine told me that he wanted to watch it again, but he couldn’t find it anywhere. It appears the official site has received a major makeover and many of the old toons are now gone. You can still find &lt;em&gt;hat show&lt;/em&gt; and others in &lt;a href=&quot;https://web.archive.org/web/20070216072551/http://www.weebls-stuff.com:80/wab/hat+show/&quot;&gt;an archived snapshot&lt;/a&gt;, but that’s pretty difficult to find with regular googling. So I’m posting it here as a reminder and hope Weebl won’t mind too much.&lt;/p&gt;</content>
  </entry>
  
  <entry>
    <title>Containerizing the infrastructure</title>
    <link href="https://permortensen.com/containerizing-the-infrastructure/"/>
    <updated>2018-07-22T00:00:00Z</updated>
    <id>https://permortensen.com/containerizing-the-infrastructure/</id>
    <author>
      <name>Per Mortensen</name>
    </author>
    <content type="html">&lt;p&gt;If you’ve been keeping an eye on my GitHub repos lately — and let’s be honest, who hasn’t — you might have noticed that several of my projects have acquired Dockerfiles.&lt;/p&gt;&lt;p&gt;The obvious reason is that I’m playing around with Docker, and since I’m the only known user of these projects I don’t really have to think about unsexy things like “compatibility” or “stability”, only insofar as “it works on my machine”. As is usually the case, I’m mostly doing this for the fun of it. Learning new tech improves my marketable skills, and if I like what I’m learning, I’ll happily spend my evenings on it. So far, I’m pleased to say that Docker is probably one of those cases.&lt;/p&gt;&lt;p&gt;It doesn’t hurt that Docker is now popular enough to be a bullet point in many job descriptions. In fact, that’s what made me give it a serious look. My current employer has some fairly “traditional” deployment procedures. Even though we run a number of different applications, the production environment consists of a handful of perpetually running Windows servers with important machine-wide configuration. So they won’t let me work with Docker on company time, but at some point I might just leave for a place that will. I then realized that regardless, I have a Docker use case all of my own. Allow me to pull back the curtain and tell you about my setup, and then rant a bit about my experience with Docker.&lt;/p&gt;&lt;p&gt;I barely get any visitors to my sites. I’m also kind of a cheapskate. For now, I run everything I need on a single $10 Ubuntu instance at Linode and, as of recently, serve it behind Cloudflare. Everything I need is currently this:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;four static sites&lt;/li&gt;&lt;li&gt;two ASP.NET Core apps&lt;/li&gt;&lt;li&gt;a Node app with Express&lt;/li&gt;&lt;li&gt;a Python app with Falcon&lt;/li&gt;&lt;li&gt;a MariaDB instance&lt;/li&gt;&lt;li&gt;a MongoDB instance&lt;/li&gt;&lt;li&gt;a Let’s Encrypt client&lt;/li&gt;&lt;li&gt;an nginx reverse proxy&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;My ultimate goal is abstracting the server away enough that my entire collection of apps and sites becomes nothing more than a bunch of containers. It would open the doors to some new options, like being able to scale up to meet demand or migrating to a new server with barely any setup (the amount of setup is why my server is still on 16.04). I’m definitely not at that point yet, but I feel like I’ve taken a big first step in that direction. All but the last two in the list above are now running on Docker, and I find that pretty cool.&lt;/p&gt;&lt;p&gt;In my opinion, Docker is a difficult ecosystem to get into, and that’s mostly down to the tools you need to know to get anything productive out of it. Learning Docker on its own is fine but it won’t really cut it for &lt;em&gt;any&lt;/em&gt; production deployments. For now I’m using &lt;a href=&quot;https://docs.docker.com/compose/&quot;&gt;docker-compose&lt;/a&gt; to manage my containers in production, but that in turn won’t cut it for more advanced things like scaling or automatic deployments. Then you might need something like &lt;a href=&quot;https://kubernetes.io/&quot;&gt;Kubernetes&lt;/a&gt; or &lt;a href=&quot;https://docs.docker.com/engine/swarm/&quot;&gt;Docker Swarm&lt;/a&gt;, which is a whole new can of worms that I don’t have the energy to open. Yet.&lt;/p&gt;&lt;p&gt;Once you start using Docker, your deployment process changes. The process for one of my apps used to be:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;Build the app in release mode&lt;/li&gt;&lt;li&gt;Upload the build output to the server&lt;/li&gt;&lt;li&gt;SSH into the server and restart the app&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;Very low-level and straightforward, right? Now, I’m deploying it like:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;Build an image of the app using a Dockerfile I previously created (which builds the app in release mode)&lt;/li&gt;&lt;li&gt;Push the image to Docker Hub (for which you need an account)&lt;/li&gt;&lt;li&gt;SSH into the server, pull the image and restart the container using docker-compose&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;It’s only slightly more work, but it involves a bunch of extra concepts that don’t affect the app itself. What’s an image? What’s a container? What’s a Dockerfile, Docker Hub, docker-compose? Well, you’ll just have to RTFM. One site that helped me understand the basics is &lt;a href=&quot;https://docker-curriculum.com/&quot;&gt;Docker curriculum&lt;/a&gt;, although it does leave out some important issues when it comes to more realistic app deployments. The official &lt;a href=&quot;https://docs.docker.com/&quot;&gt;Docker documentation&lt;/a&gt; is pretty solid too, but it’s more of a reference than a gentle introduction.&lt;/p&gt;&lt;p&gt;For some things you can’t just RTFM though, and to me it was mainly &lt;em&gt;how do I provide configuration files to containers&lt;/em&gt;? I obviously don’t want my production secrets in a public image, but I do need to provide them. It seems the official recommendation is using environment variables for configuration, but as nice and cross-platform as they are, they’re not really useful beyond simple key-value structures. Also, I don’t want to make too many changes to my apps in the name of containerization. If one day I decide that Docker isn’t for me, I want an easy out; I don’t want my app tightly coupled to its deployment mechanism. What I ended up doing for this particular problem was storing private configuration next to the app’s docker-compose configuration and bind mounting it into the right place. I find it to be a natural place to put it since my docker-compose configurations are also generally private.&lt;/p&gt;&lt;p&gt;Perhaps my favorite feature of Docker is the Dockerfile, which is a recipe for building an app as a Docker image. It’s basically a sequence of repeatable, easy-to-read instructions that can be distributed along with the source code. It makes the app’s system requirements very explicit and at the same time allows anyone to build it without having to deal with them. Imagine in five years from now you need to figure out what global packages your old app needs. Instead of reverse-engineering your old server or trial-and-erroring until it sort of works, you can just read your Dockerfile and be done with it.&lt;/p&gt;&lt;p&gt;My Docker transformation is still a work in progress, and the main thing missing right now is getting nginx running on it. I imagine it will be the most cumbersome conversion since my nginx setup has a bunch of configuration and brings all my sites down if it misbehaves. But if a tree falls in the forest and no one is around to hear it…&lt;/p&gt;</content>
  </entry>
  
  <entry>
    <title>Marble Hornets returns, but not really</title>
    <link href="https://permortensen.com/marble-hornets-returns-but-not-really/"/>
    <updated>2018-01-24T00:00:00Z</updated>
    <id>https://permortensen.com/marble-hornets-returns-but-not-really/</id>
    <author>
      <name>Per Mortensen</name>
    </author>
    <content type="html">&lt;p&gt;A surprising turn of events for the guy(s) behind Marble Hornets.&lt;/p&gt;&lt;p&gt;If you’ve read &lt;a href=&quot;https://permortensen.com/the-rise-and-fall-of-a-minor-youtube-group/&quot;&gt;my little writeup&lt;/a&gt; from 2016 on the group formerly known as THAC, you know that due to a rather sad series of events, it ended up consisting of only its founding member: Troy Wagner. Soon after, Troy rebranded as &lt;em&gt;Fish With A Nose&lt;/em&gt;, and then again later as &lt;em&gt;Grampo Co.&lt;/em&gt;, which currently has just two bland animation shorts on &lt;a href=&quot;https://www.youtube.com/grampoco&quot;&gt;its main YouTube channel&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;After the mid-breakup &lt;em&gt;Clear Lakes 44&lt;/em&gt; project failed to gain traction, Troy cancelled it and launched a similar horror/mystery series called &lt;a href=&quot;https://www.youtube.com/eckvanet&quot;&gt;&lt;em&gt;ECKVA&lt;/em&gt;&lt;/a&gt;, which is now Troy’s main focus. ECKVA has been received significantly better by the community, including &lt;a href=&quot;https://www.youtube.com/watch?v=eEAX2J1Kerw&quot;&gt;a very positive review by Night Mind&lt;/a&gt;, but in terms of viewership it still falls short of the high bar set by Marble Hornets, currently receiving an average of just 25K views per video.&lt;/p&gt;&lt;p&gt;It’s now been three and a half years since Marble Hornets ended. On December 27, Troy tweeted this:&lt;/p&gt;&lt;blockquote class=&quot;twitter-tweet&quot; data-lang=&quot;en&quot;&gt;&lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&gt;Smile for the camera &lt;a href=&quot;https://t.co/4rNi4OFo2w&quot;&gt;pic.twitter.com/4rNi4OFo2w&lt;/a&gt;&lt;/p&gt;&amp;mdash; Troy Wagner (@whatatroy) &lt;a href=&quot;https://twitter.com/whatatroy/status/946129758101954563?ref_src=twsrc%5Etfw&quot;&gt;December 27, 2017&lt;/a&gt;&lt;/blockquote&gt;&lt;script async=&quot;&quot; src=&quot;https://platform.twitter.com/widgets.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;&lt;p&gt;In case you don’t recognize the mask, it basically means Marble Hornets will return in 2018. Very intriguing for sure, but how exactly will it make a return with only a single member of the cast and crew remaining? Troy stated in another tweet that the new project &lt;a href=&quot;https://twitter.com/whatatroy/status/950451276080402434&quot;&gt;will not impact ECKVA&lt;/a&gt;, so how exactly will he pull off running two editing-heavy webseries at the same time?&lt;/p&gt;&lt;p&gt;Well, because according to &lt;a href=&quot;https://jtroyw.blogspot.com/2018/01/the-return.html&quot;&gt;his latest blog entry&lt;/a&gt;, it’s going to be a comic series. Huh. Okay. That was unexpected.&lt;/p&gt;&lt;p&gt;At this point it’s important to realize that key components to Marble Hornets’ success are impossible to translate to other formats. For my money, its strong points were the unique presentation and the frequent social media interactivity, which at times made it feel more like an ARG than a webseries. It was very much &lt;em&gt;not&lt;/em&gt; the characters or dialogue, which unfortunately claimed more and more screen time as the show went on. I hope Troy is aware of this, and I really hope he’s not going to produce the comic by just drawing entries instead of filming them. It’s going to take some pretty off-beat creative elements to elevate it above a collection of vaguely recognizable Marble Hornets material with all the good bits lost in translation.&lt;/p&gt;&lt;p&gt;In related news, Tim is back! He originally &lt;a href=&quot;https://www.youtube.com/watch?v=s9xoKJYDLVg&quot;&gt;teased a return&lt;/a&gt; in early 2016, but it would be another year and a half before he finally delivered &lt;a href=&quot;https://www.youtube.com/watch?v=46jk4wlqYDw&quot;&gt;a self-aware reboot of Space Time Express&lt;/a&gt; in December 2017:&lt;/p&gt;&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/46jk4wlqYDw&quot; frameborder=&quot;0&quot; allow=&quot;autoplay; encrypted-media&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;&lt;p&gt;He’s got &lt;a href=&quot;https://www.patreon.com/timsutton&quot;&gt;a Patreon&lt;/a&gt; too, which seems kind of premature for someone with two uploads 16 months apart, but if it incentivizes him to continue making videos, I’m all for it. In my opionion, Tim is the most naturally funny member of what used to be THAC and probably has the best shot of any of them at producing quality comedy shorts.&lt;/p&gt;</content>
  </entry>
  
  <entry>
    <title>It&#39;ll be our dirty little secret</title>
    <link href="https://permortensen.com/itll-be-our-dirty-little-secret/"/>
    <updated>2017-12-19T22:21:56Z</updated>
    <id>https://permortensen.com/itll-be-our-dirty-little-secret/</id>
    <author>
      <name>Per Mortensen</name>
    </author>
    <content type="html">&lt;p&gt;Here’s another update on what I’ve been doing lately, for the 1 person who follows my blog. Hi mom! Just kidding. My mom doesn’t read my blog.&lt;/p&gt;&lt;p&gt;Years ago, I joined a tiny IRC channel on a whim and ended up liking the people so much that I stayed there ever since, and I have even visited some of them in real life. At one point I made an IRC bot called &lt;a href=&quot;https://github.com/proog/nda&quot;&gt;nda&lt;/a&gt; specifically for the channel. IRC bots are the classic &lt;em&gt;Baby’s first Python program&lt;/em&gt;, but for once it became a thing everyone actually liked (or at least tolerated). It did a bunch of things, but its best features were arguably &lt;em&gt;!quote&lt;/em&gt; and &lt;em&gt;!porn&lt;/em&gt;.&lt;/p&gt;&lt;p&gt;&lt;em&gt;!quote&lt;/em&gt; was a system that would record every message in the channel and save it to a SQLite database. You could then query it using the &lt;em&gt;!quote&lt;/em&gt; command, which would give you a random message from the entire collection. I wrote a simple script that would import years and years of chat logs acquired from one of the channel founders, so it ended up with a table of over a million messages to choose from. I initially ran nda on a Raspberry Pi under my desk, but the large quantity of quote data soon meant that I had to move it to the cloud to have any semblance of responsiveness. nda was single-threaded and blocked the event loop for the duration of the (quite expensive) database query.&lt;/p&gt;&lt;p&gt;Side note: the queries were expensive because the only way to get a random row from an arbitrary result set is doing &lt;code&gt;ORDER BY RANDOM() LIMIT 1&lt;/code&gt;. There are a number of tricks to improve on this, and for nda, the best solution I came up with was:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;perform a &lt;code&gt;SELECT COUNT(*)&lt;/code&gt; of the matched rows as &lt;code&gt;count&lt;/code&gt;&lt;/li&gt;&lt;li&gt;find a random number &lt;code&gt;r&lt;/code&gt; in the range &lt;code&gt;0 ≤ r &amp;lt; count&lt;/code&gt;&lt;/li&gt;&lt;li&gt;perform the actual &lt;code&gt;SELECT *&lt;/code&gt; using &lt;code&gt;LIMIT 1 OFFSET r&lt;/code&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;This avoids ordering the entire result set at the cost of performing a count, which is pretty fast, and skipping through a number of rows, which is slower the larger &lt;code&gt;r&lt;/code&gt; happens to be. My solution had decent performance for the use case, but in retrospect, &lt;a href=&quot;https://stackoverflow.com/a/24591688/2182896&quot;&gt;this strategy&lt;/a&gt; would have been even faster had I thought of it.&lt;/p&gt;&lt;p&gt;The other feature, aptly named &lt;em&gt;!porn&lt;/em&gt;, was pretty simple in comparison: you typed in &lt;em&gt;!porn&lt;/em&gt; and it gave you a link to a random adult video and the longest user comment on that video. Okay, okay, I know how it sounds. But it’s a lot of fun. I don’t want to kinkshame anyone, but some people practically write entire novels in the comment section of porn sites – &lt;strong&gt;which I know exclusively from the professional research conducted in order to develop this feature&lt;/strong&gt; – and it can be absolutely hilarious.&lt;/p&gt;&lt;p&gt;So, how would you go about finding a random video? For some sites, like Reddit or Imgur, it’s easy: you either have an API to help you out, or the URL pattern is simple enough to just brute force with random combinations until you find a link that works. But for the adult sites I investigated, they all used a much more complicated pattern, and porn APIs weren’t really a thing. For instance, Xhamster used a combination of the video title and a unique id in its URLs, which is impossible to crack by blindly hammering away at it. But the good thing about Xhamster: their randomizer button that just gave you a 302 to a random video.&lt;/p&gt;&lt;p&gt;Once you have a link, you can fetch the page and extract comments from it. The Python standard library has everything, including an HTML parser, and nda used this to parse the title and comments out of whatever video page was sent back. Thankfully, the layout stayed relatively stable and rarely broke the &lt;em&gt;!porn&lt;/em&gt; feature. To remain inconspicuous, it spoofed the &lt;code&gt;User-Agent&lt;/code&gt; header by using a couple of widely used identifiers, though in retrospect, they would probably just block the IP if it somehow became enough of a nuisance.&lt;/p&gt;&lt;p&gt;Over a year ago, the IRC channel became a WhatsApp group. A few people continued to use both, but WhatsApp was the only reliable way to reach everyone, and eventually we all abandoned the channel. nda stayed in the channel until all regular users had left, and was finally shut down for good last month. The channel registration has now expired. But I’m excited to say that the spirit of nda lives on in two spinoff projects based on its two best features!&lt;/p&gt;&lt;p&gt;&lt;em&gt;!quote&lt;/em&gt; lives on in &lt;a href=&quot;https://github.com/proog/quote-importer&quot;&gt;quote-importer&lt;/a&gt;, which expands upon nda’s quote database. It focuses on capturing and storing as much information as possible from chat logs instead of fast random access. It can currently parse Irssi logs, WhatsApp logs, and even nda’s logs, and saves several types of events; not just messages. It can then store them in various database systems like SQLite, MySQL, and MongoDB. I have recently used it to parse and import the entire catalog of our group’s IRC and WhatsApp logs into a single database, which currently clocks in at just about 3.4 million objects.&lt;/p&gt;&lt;p&gt;&lt;em&gt;!porn&lt;/em&gt; lives on in &lt;a href=&quot;https://github.com/proog/talkdirty2me&quot;&gt;talkdirty2me&lt;/a&gt;, which is a small .NET Core/Vue web app (&lt;a href=&quot;https://dirty.per.computer&quot;&gt;live demo here&lt;/a&gt;, turn up the volume). It works much like the original feature did: it gets you a random video and its longest comment. It also saves the comment and generates a shareable permalink that works nicely in rich previews. Xhamster remains a valuable source of quality comments, but there’s an important technical update: Xhamster now has a REST API! &lt;em&gt;I know!!&lt;/em&gt; It’s undocumented and not particularly pretty, but a recent update to their site uses it to – you guessed it – fetch comments. You no longer have to parse HTML to get the comments for a video. If you have the video’s id (which can be extracted from the URL) and a few secret headers up your sleeve, you can get its comments in a paged JSON list, which is a huge upgrade from trying to match element ids and CSS classes. I haven’t yet found an endpoint that gives me the video title, so for now, a small amount of HTML fiddling is done with a regular expression.&lt;/p&gt;&lt;p&gt;So that’s what I’ve been up to recently. As I mentioned before, I’ve also rewritten &lt;a href=&quot;https://crimsonwars.net&quot;&gt;crimsonwars.net&lt;/a&gt; as a static Jekyll site. Even though it’s technically simple, I’m still proud of it and want to make it public on Github. I just need to separate the somewhat questionable downloads from the rest of the site so they don’t endanger my account. More on that later, maybe!&lt;/p&gt;</content>
  </entry>
  
  <entry>
    <title>Headless wifi for the Pi</title>
    <link href="https://permortensen.com/headless-wifi-for-the-pi/"/>
    <updated>2017-11-29T22:00:57Z</updated>
    <id>https://permortensen.com/headless-wifi-for-the-pi/</id>
    <author>
      <name>Per Mortensen</name>
    </author>
    <content type="html">&lt;p&gt;I was setting up a headless Raspberry Pi today and was happy to find that you can preload wifi settings onto it, removing the need for any physical interaction with the thing. But there’s a trick to it.&lt;/p&gt;&lt;p&gt;Ideally, you should just be able to add a &lt;code&gt;wpa_supplicant.conf&lt;/code&gt; file with your network settings to the &lt;code&gt;boot&lt;/code&gt; partition, and the Pi will move it into &lt;code&gt;/etc/wpa_supplicant/&lt;/code&gt; for you on boot and have Raspbian automatically connect with those settings.&lt;/p&gt;&lt;p&gt;Some of the most popular answers for this topic provide you with a &lt;code&gt;wpa_supplicant.conf&lt;/code&gt; example that contains nothing but the &lt;code&gt;network&lt;/code&gt; block. Unfortunately, those examples aren’t quite complete for practical usage. As it turns out, there are a couple of magic words that make the difference between having a networked, headless machine and a brick with lights on it. As of November 2017, you need something like this:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-conf&quot;&gt;country=GB
ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev
update_config=1

network={
    ssid=&amp;quot;Your wifi name&amp;quot;
    psk=&amp;quot;Your wifi password&amp;quot;
    key_mgmt=WPA-PSK
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Those first three lines were taken from the default &lt;code&gt;wpa_supplicant.conf&lt;/code&gt; found on a fresh image of Raspbian. In my situation, it seems that the &lt;code&gt;country&lt;/code&gt; line is not actually needed, though your mileage may vary.&lt;/p&gt;&lt;p&gt;And despite what some people out there on the web may claim, placing the file on the &lt;code&gt;boot&lt;/code&gt; partition does work as intended with Raspbian Lite. Remember to also add an &lt;code&gt;ssh&lt;/code&gt; file so you can actually mess with it once it’s online!&lt;/p&gt;</content>
  </entry>
  
  <entry>
    <title>CrimsonWars.net is up</title>
    <link href="https://permortensen.com/crimsonwars-net-is-up/"/>
    <updated>2017-10-02T17:26:54Z</updated>
    <id>https://permortensen.com/crimsonwars-net-is-up/</id>
    <author>
      <name>Per Mortensen</name>
    </author>
    <content type="html">&lt;p&gt;I love my little side projects, but they always end up being some sort of interactive app. I want to make more regular websites, but what can I possibly make that hasn’t already been made a thousand times before?&lt;/p&gt;&lt;p&gt;Well, there’s an old game I remember from my childhood. It’s called &lt;em&gt;Crimson Wars&lt;/em&gt;, a game that’s probably not very good, but still sparks nostalgic feelings whenever I remember that it exists. The thing is, information about it online is close to non-existent. There are some gameplay videos and some game disc rips, but that’s pretty much it. No wiki, no guides, no lore.&lt;/p&gt;&lt;p&gt;So now I have created &lt;a href=&quot;https://crimsonwars.net/&quot;&gt;crimsonwars.net&lt;/a&gt;, the one stop shop for all your Crimson Wars needs! I imagine very few people are interested in it, but the domain was cheap and I had fun making it.&lt;/p&gt;&lt;p&gt;It’s made with ASP.NET Core 2 and &lt;a href=&quot;https://docs.microsoft.com/en-us/aspnet/core/mvc/razor-pages&quot;&gt;Razor pages&lt;/a&gt;, which is something I’ve been wanting to try out but haven’t had a good use case for. At first I was concerned that it would require copious amounts of boilerplate and high ceremony, but I was pleasantly surprised how easy working with it turned out to be. Razor pages seem to be extremely well suited for making mostly static content where you still want some form of reusability and customization. It basically allows you to write only &lt;code&gt;.cshtml&lt;/code&gt; files and transparently handles controller actions and routing for you.&lt;/p&gt;&lt;p&gt;Anyway, this was just a quick note about what I’ve been working on lately. I hope to be doing more content-focused websites like this in the future.&lt;/p&gt;&lt;p&gt;&lt;em&gt;Update 2017-12-11: In the interest of simplification and trying out new things, I have rewritten the thing as a completely static site – which it kind of was anyway – using &lt;a href=&quot;https://jekyllrb.com/&quot;&gt;Jekyll&lt;/a&gt; and a lot of Markdown.&lt;/em&gt;&lt;/p&gt;</content>
  </entry>
  
  <entry>
    <title>PIA on a Pi</title>
    <link href="https://permortensen.com/pia-on-a-pi/"/>
    <updated>2017-06-26T15:35:53Z</updated>
    <id>https://permortensen.com/pia-on-a-pi/</id>
    <author>
      <name>Per Mortensen</name>
    </author>
    <content type="html">&lt;p&gt;Here’s a short guide to setting up a VPN on Linux — specifically &lt;a href=&quot;https://www.privateinternetaccess.com/&quot;&gt;Private Internet Access&lt;/a&gt; on a Raspberry Pi, because I’m mostly writing this as a reminder to myself — with a kill switch to prevent non-VPN traffic. I’m targeting a Pi 3 with Raspbian Jessie here, but the procedure should be similar for other machines and distros as long as packages like OpenVPN and UFW are available.&lt;/p&gt;&lt;h2&gt;OpenVPN setup&lt;/h2&gt;&lt;p&gt;Install OpenVPN.&lt;/p&gt;&lt;pre&gt;&lt;code&gt;# apt-get install openvpn
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Download the VPN provider’s OpenVPN configuration files. For PIA, this usually means &lt;a href=&quot;https://www.privateinternetaccess.com/openvpn/openvpn.zip&quot;&gt;this one&lt;/a&gt;.&lt;/p&gt;&lt;pre&gt;&lt;code&gt;$ curl -O https://www.privateinternetaccess.com/openvpn/openvpn.zip
$ unzip -d openvpn openvpn.zip
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;OpenVPN on Linux uses &lt;code&gt;.conf&lt;/code&gt; for config files instead of &lt;code&gt;.ovpn&lt;/code&gt;, so rename them accordingly.&lt;/p&gt;&lt;pre&gt;&lt;code&gt;$ rename &#39;s/ovpn/conf/&#39; openvpn/*.ovpn
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;To enable automatic authentication when connecting, put your VPN username and password in a file next to the configs.&lt;/p&gt;&lt;pre&gt;&lt;code&gt;$ echo &#39;CoolGuy123
s3cr3t&#39; &amp;gt; openvpn/auth.txt
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;It’s not very secure, so add a little protection by limiting access to the owner.&lt;/p&gt;&lt;pre&gt;&lt;code&gt;$ chmod 600 openvpn/auth.txt
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Next, make the configurations refer to this file by appending some directives at the end of each. Here I’ll also throw in a couple of quality tweaks such as keepalive, logging to make troubleshooting easier, and automatic execution of a script called &lt;code&gt;update-resolv-conf&lt;/code&gt;, which might be necessary for DNS resolution to work correctly when turning the VPN on and off. On Debian, this script is included in the OpenVPN install.&lt;/p&gt;&lt;pre&gt;&lt;code&gt;$ echo &#39;auth-user-pass auth.txt
keepalive 10 60
log-append /var/log/openvpn.log
script-security 2
up /etc/openvpn/update-resolv-conf
down /etc/openvpn/update-resolv-conf&#39; | tee -a openvpn/*.conf
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;From here, it might make sense to continue as &lt;code&gt;root&lt;/code&gt;. Move everything into place by copying the auth file, configurations, certificate and key file to &lt;code&gt;/etc/openvpn&lt;/code&gt;.&lt;/p&gt;&lt;pre&gt;&lt;code&gt;# cp openvpn/* /etc/openvpn
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;To make OpenVPN automatically connect with a certain configuration, set the &lt;code&gt;AUTOSTART&lt;/code&gt; directive in &lt;code&gt;/etc/default/openvpn&lt;/code&gt; to the configuration filename without the extension.&lt;/p&gt;&lt;pre&gt;&lt;code&gt;# echo &#39;AUTOSTART=&amp;quot;Germany&amp;quot;&#39; &amp;gt;&amp;gt; /etc/default/openvpn
&lt;/code&gt;&lt;/pre&gt;&lt;blockquote&gt;&lt;p&gt;&lt;em&gt;Note 2020-03-14:&lt;/em&gt; since this post was first published, there’s &lt;a href=&quot;https://community.openvpn.net/openvpn/wiki/Systemd#UsingOpenVPNwithsystemdsupport&quot;&gt;a better way to run OpenVPN as a service&lt;/a&gt;. Instead of using &lt;code&gt;/etc/default/openvpn&lt;/code&gt;, move all the configuration files one level down into &lt;code&gt;/etc/openvpn/client&lt;/code&gt; and enable the service by calling &lt;code&gt;systemctl enable openvpn-client@Germany&lt;/code&gt;. I’ve tested this on Raspbian Buster, but I’m unsure if it works on older versions as well.&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;It should now be possible to start up OpenVPN and have it connect.&lt;/p&gt;&lt;pre&gt;&lt;code&gt;# systemctl daemon-reload
# systemctl restart openvpn
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;To verify this, wait a few seconds and run &lt;code&gt;ifconfig&lt;/code&gt;. There should be an interface called &lt;code&gt;tun0&lt;/code&gt; or similar (for &lt;em&gt;tunnel&lt;/em&gt;), which is the VPN interface. To test connectivity, call an IP echo service such as &lt;a href=&quot;https://ipinfo.io/&quot;&gt;ipinfo.io&lt;/a&gt;, which should report an IP and a country matching your configuration.&lt;/p&gt;&lt;pre&gt;&lt;code&gt;$ curl ipinfo.io
&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;UFW kill switch&lt;/h2&gt;&lt;p&gt;At this point, if the VPN connection drops or OpenVPN stops for some reason, your machine will talk freely over your regular, non-VPN interface. Depending on the use case, it might be preferable for it to stop communicating completely instead, like if you’re &lt;s&gt;downloading warez&lt;/s&gt; concerned about your privacy.&lt;/p&gt;&lt;p&gt;To ensure this, we’re going to set up a firewall to deny everything but the VPN handshake on the regular interfaces &lt;code&gt;eth0&lt;/code&gt; and &lt;code&gt;wlan0&lt;/code&gt; while placing no restrictions on &lt;code&gt;tun0&lt;/code&gt;. I’m going to use UFW because it’s fairly intuitive compared to iptables.&lt;/p&gt;&lt;pre&gt;&lt;code&gt;# apt-get install ufw
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Next we fill in the firewall rules. First, allow everything on OpenVPN’s network interface.&lt;/p&gt;&lt;pre&gt;&lt;code&gt;# ufw allow in on tun0
# ufw allow out on tun0
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;OpenVPN obviously needs to be able to connect on the regular interface, and with the default PIA configuration files, this means allowing&lt;/p&gt;&lt;ol&gt;&lt;li&gt;DNS resolution on port 53 to find e.g. &lt;code&gt;germany.privateinternetaccess.com&lt;/code&gt;&lt;/li&gt;&lt;li&gt;The actual VPN port specified by the configuration files, i.e. 1198&lt;/li&gt;&lt;/ol&gt;&lt;!-- hack to fix formatting of the following block --&gt;&lt;pre&gt;&lt;code&gt;# ufw allow out on eth0 from any to any port 53
# ufw allow out on wlan0 from any to any port 53
# ufw allow out on eth0 from any to any port 1198
# ufw allow out on wlan0 from any to any port 1198
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;You might also want to have the machine reachable from the local network to SSH into it and such, so allow everything to those IPs. My local network has IPs in the 192.168.0.* range so I’ll be using that.&lt;/p&gt;&lt;pre&gt;&lt;code&gt;# ufw allow in on eth0 from 192.168.0.0/24 to any
# ufw allow in on wlan0 from 192.168.0.0/24 to any
# ufw allow out on eth0 from any to 192.168.0.0/24
# ufw allow out on wlan0 from any to 192.168.0.0/24
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Finally, block everything else and enable the firewall.&lt;/p&gt;&lt;pre&gt;&lt;code&gt;# ufw deny in on eth0
# ufw deny in on wlan0
# ufw deny out on eth0
# ufw deny out on wlan0
# ufw enable
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Now test this by stopping OpenVPN and sending a request:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;# systemctl stop openvpn
# curl --connect-timeout 5 ipinfo.io
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Because DNS is allowed outside the VPN, ipinfo.io is resolved, but the request times out after 5 seconds because port 80 is blocked. Turning OpenVPN on again restores connectivity.&lt;/p&gt;&lt;p&gt;And that’s basically it. Easy, right? Not really, but it works fairly well.&lt;/p&gt;</content>
  </entry>
  
  <entry>
    <title>Sometimes, the internet does forget</title>
    <link href="https://permortensen.com/sometimes-the-internet-does-forget/"/>
    <updated>2017-01-31T16:20:00Z</updated>
    <id>https://permortensen.com/sometimes-the-internet-does-forget/</id>
    <author>
      <name>Per Mortensen</name>
    </author>
    <content type="html">&lt;p&gt;Remember that old adage that goes something like, &lt;em&gt;once you put it on the internet, it will be there forever&lt;/em&gt;? Well, sometimes it won’t. And then at some point, maybe it will. Wait, what?&lt;/p&gt;&lt;p&gt;Back in 8th grade (which would be 2006 or so), I stumbled upon a series of crudely made internet videos by a guy named David Baker. David, who I assume was in his mid to late teens at the time, had made 11 short episodes of a cartoon about &lt;em&gt;Super Go Up Stairs Man&lt;/em&gt;, a spoof anime superhero whose super power was the ability to go up (and fall down) stairs. The episodes ranged from basic plots to complete nonsense, but usually centered on the titular character and his arch nemesis the Jeep (who was supposedly evil). From the looks of it, it was made entirely with Microsoft Paint, Windows Movie Maker, and stock photos from Google image search. As low-budget (but not necessarily low-effort) as it gets.&lt;/p&gt;&lt;p&gt;In addition to the videos, which were hosted on Google Video (remember that?), &lt;em&gt;SGUSM&lt;/em&gt; had an official webpage on one of the free web hosts that were popular back then, like Angelfire or Freewebs (remember &lt;em&gt;those&lt;/em&gt;?). Even for 2006, it looked terrible. Its primary colors were pink and black, it had Javascript snowflakes falling down the page, and I’m pretty sure it also played a MIDI. Whether or not all this was &lt;em&gt;intentionally&lt;/em&gt; bad is anyone’s guess — I would say yes — but it sure was quite a sight.&lt;/p&gt;&lt;p&gt;Myself and a couple of my classmates found &lt;em&gt;SGUSM&lt;/em&gt; hilarious and lines from the show quickly became little inside jokes. That was a decade ago, and I hardly talk to any of them these days. But every once in a while I feel like taking a trip down memory lane. Imagine my surprise when today, the Google results looked like this:&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://permortensen.com/assets/images/dist/czGyxuEzcZ-1512.png&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://permortensen.com/assets/images/dist/czGyxuEzcZ-800.webp 800w, https://permortensen.com/assets/images/dist/czGyxuEzcZ-1200.webp 1200w, https://permortensen.com/assets/images/dist/czGyxuEzcZ-1512.webp 1512w&quot; sizes=&quot;(max-width: 65ch) 100vw, 65ch&quot;&gt;&lt;img src=&quot;https://permortensen.com/assets/images/dist/czGyxuEzcZ-800.png&quot; alt=&quot;1 result&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1512&quot; height=&quot;547&quot; srcset=&quot;https://permortensen.com/assets/images/dist/czGyxuEzcZ-800.png 800w, https://permortensen.com/assets/images/dist/czGyxuEzcZ-1200.png 1200w, https://permortensen.com/assets/images/dist/czGyxuEzcZ-1512.png 1512w&quot; sizes=&quot;(max-width: 65ch) 100vw, 65ch&quot;&gt;&lt;/picture&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;Apart from random strings of characters, this might be the emptiest search page I’ve ever seen. And no, the one result is nothing more than a highly outdated list of Google Video links. Were we the only people actually watching &lt;em&gt;SGUSM&lt;/em&gt; back then? I have no view counts to refer to, so who knows. In any case, the videos seem to be gone from the surface of the web. As a person who cares way too much about preserving old data, this makes me very sad.&lt;/p&gt;&lt;p&gt;But then I remembered: at some point, I saved all the episodes to my computer. Even though my computer from 2006 is long gone, much of the data from that time has been archived on several generations of external hard drives. So here I’ll post what I believe to be the entire series, and the internet recalls one very insignificant memory. I’m obviously doing this without permission from the creator. David, if by any chance you’re reading this far off in the future, be sure to drop me a line. &lt;em&gt;Update 2017-05-23: now with permission. Thanks Dave!&lt;/em&gt;&lt;/p&gt;&lt;h2&gt;&lt;em&gt;Super Go Up Stairs Man&lt;/em&gt; Series 1&lt;/h2&gt;&lt;h3&gt;Episode 1: &lt;em&gt;Hard street being in sometime&lt;/em&gt;&lt;/h3&gt;&lt;p&gt;&lt;video controls=&quot;&quot; preload=&quot;none&quot; src=&quot;https://static.per.computer/sgusm/ep101.mp4&quot;&gt;&lt;/video&gt;&lt;/p&gt;&lt;h3&gt;Episode 2: &lt;em&gt;If Heerotishimi desert bin gen then fire breathe from Dragon nextime&lt;/em&gt;&lt;/h3&gt;&lt;p&gt;&lt;video controls=&quot;&quot; preload=&quot;none&quot; src=&quot;https://static.per.computer/sgusm/ep102.mp4&quot;&gt;&lt;/video&gt;&lt;/p&gt;&lt;h3&gt;Episode 3: &lt;em&gt;No Evil&lt;/em&gt;&lt;/h3&gt;&lt;p&gt;&lt;video controls=&quot;&quot; preload=&quot;none&quot; src=&quot;https://static.per.computer/sgusm/ep103.mp4&quot;&gt;&lt;/video&gt;&lt;/p&gt;&lt;h3&gt;Episode 4: &lt;em&gt;Map E&lt;/em&gt;&lt;/h3&gt;&lt;p&gt;&lt;video controls=&quot;&quot; preload=&quot;none&quot; src=&quot;https://static.per.computer/sgusm/ep104.mp4&quot;&gt;&lt;/video&gt;&lt;/p&gt;&lt;h3&gt;Episode 5: &lt;em&gt;Jeep&lt;/em&gt;&lt;/h3&gt;&lt;p&gt;&lt;video controls=&quot;&quot; preload=&quot;none&quot; src=&quot;https://static.per.computer/sgusm/ep105.mp4&quot;&gt;&lt;/video&gt;&lt;/p&gt;&lt;h3&gt;Episode 6: &lt;em&gt;The Credit Pirates&lt;/em&gt;&lt;/h3&gt;&lt;p&gt;&lt;video controls=&quot;&quot; preload=&quot;none&quot; src=&quot;https://static.per.computer/sgusm/ep106.mp4&quot;&gt;&lt;/video&gt;&lt;/p&gt;&lt;h3&gt;Episode 7 (Japanese TV show)&lt;/h3&gt;&lt;p&gt;&lt;video controls=&quot;&quot; preload=&quot;none&quot; src=&quot;https://static.per.computer/sgusm/ep107.mp4&quot;&gt;&lt;/video&gt;&lt;/p&gt;&lt;h3&gt;Episode 8 (Lift)&lt;/h3&gt;&lt;p&gt;&lt;video controls=&quot;&quot; preload=&quot;none&quot; src=&quot;https://static.per.computer/sgusm/ep108.mp4&quot;&gt;&lt;/video&gt;&lt;/p&gt;&lt;h2&gt;&lt;em&gt;Super Go Up Stairs Man&lt;/em&gt; Series 2&lt;/h2&gt;&lt;h3&gt;Trailer&lt;/h3&gt;&lt;p&gt;&lt;video controls=&quot;&quot; preload=&quot;none&quot; src=&quot;https://static.per.computer/sgusm/s2trailer.mp4&quot;&gt;&lt;/video&gt;&lt;/p&gt;&lt;h3&gt;Episode 1: &lt;em&gt;The People Talking Are The Superhero Council&lt;/em&gt;&lt;/h3&gt;&lt;p&gt;&lt;video controls=&quot;&quot; preload=&quot;none&quot; src=&quot;https://static.per.computer/sgusm/ep201.mp4&quot;&gt;&lt;/video&gt;&lt;/p&gt;&lt;h3&gt;Episode 2: &lt;em&gt;NEWS&lt;/em&gt;&lt;/h3&gt;&lt;p&gt;&lt;video controls=&quot;&quot; preload=&quot;none&quot; src=&quot;https://static.per.computer/sgusm/ep202.mp4&quot;&gt;&lt;/video&gt;&lt;/p&gt;&lt;h3&gt;Episode 3: &lt;em&gt;Is It Snowy, Sure!&lt;/em&gt;&lt;/h3&gt;&lt;p&gt;&lt;video controls=&quot;&quot; preload=&quot;none&quot; src=&quot;https://static.per.computer/sgusm/ep203.mp4&quot;&gt;&lt;/video&gt;&lt;/p&gt;</content>
  </entry>
  
  <entry>
    <title>Simplification is a good thing</title>
    <link href="https://permortensen.com/simplification-is-a-good-thing/"/>
    <updated>2016-12-18T10:47:00Z</updated>
    <id>https://permortensen.com/simplification-is-a-good-thing/</id>
    <author>
      <name>Per Mortensen</name>
    </author>
    <content type="html">&lt;p&gt;Although PHP is (with good reason) regarded as a bad language, it holds a special place in my heart. It was the first useful programming language I learned when I started getting into coding in my teens, and since then I have used it for everything from simple dynamic pages to large ORM-powered APIs. The former is definitely the problem that PHP solves best, as there is practically no setup or boilerplate required to get a page up and running (if you discount the HTTP server). It’s a technology that is in my experience still unmatched on ease of use for that purpose. Sadly, the web of 2016 has little use for such simple things, and over the years PHP has borrowed so many concepts from other languages that modern PHP has mostly ended up a slightly worse version of them. It has classes, but little type safety. It has collection libraries, but no generics. It has lambdas, but no terse syntax for them. And yet, because of its ubiquity, it’s still widely used for relatively complex systems around the web.&lt;/p&gt;&lt;p&gt;My personal web projects were historically PHP-based because I grew up on free web hosts that allowed PHP, MySQL, &lt;em&gt;and nothing else&lt;/em&gt;. But for the last 18 months this site has been running on a $10 Digital Ocean droplet that hosts everything I need, from &lt;a href=&quot;https://ghost.org/&quot;&gt;Ghost&lt;/a&gt; to &lt;a href=&quot;https://github.com/proog/nda&quot;&gt;nda&lt;/a&gt; to a custom personal finance tool and more. Over time, I retired or replaced most of the stuff that used PHP; not because I specifically wanted to get rid of it, but as a natural consequence of changing interests. But until today, there was one application of mine still relying on a LAMP stack to function, and that’s &lt;a href=&quot;https://github.com/proog/g_&quot;&gt;my game collection app&lt;/a&gt; that I sort of mentioned &lt;a href=&quot;https://permortensen.com/long-time-no-write/&quot;&gt;almost two years ago&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;This irked me a bit. The only reason I kept Apache on my server was to support this single thing. nginx is on proxy duty anyway, so static files could be served just as well by that. And making a significant change to the underlying technology is one of the freedoms you are afforded when no one but you depends on the project. So for the last month, I’ve been porting the entire backend to something else: ASP.NET Core with Entity Framework Core. C# is a good language and I’m very happy to see that Microsoft is committed to making .NET (and important frameworks) cross-platform. I also haven’t used .NET extensively in a web context before, so it’s been a great learning experience.&lt;/p&gt;&lt;p&gt;The rewrite also made me realize that some architectural decisions didn’t make sense anymore. The old list evolved from a simple &lt;code&gt;mysql_query()&lt;/code&gt; call on a shared web host where MySQL was the only option, but of course, that’s no longer a requirement (and realistically hasn’t been since it started using tools like Composer and npm). Relying on MySQL also comes with its own issues, namely having to install and run &lt;code&gt;mysqld&lt;/code&gt; if you aren’t already. So while we’re at it, we might as well replace MySQL with something more suited for the task. I can think of no better option than SQLite: it’s zero-configuration, it’s easy to manage, and it has first-class support in EF Core. Using SQLite not only simplifies the system requirements for the application, it also simplifies configuration: Previously, you would have to enter a database address, name, credentials, and a table prefix in addition to app-specific options. With SQLite, everything but the last one can be skipped because it’s self-contained and uses no access control mechanism beside the filesystem itself.&lt;/p&gt;&lt;p&gt;Another (unrelated) simplification I implemented in this version was replacing Gulp with npm as the frontend build tool. Gulp seems like a good tool if you’re doing large, multi-target build tasks, but in the case of g_, the only task I need is concatenating and minifying Javascript files. I recently read an interesting article on how to &lt;a href=&quot;https://www.keithcirkel.co.uk/how-to-use-npm-as-a-build-tool/&quot;&gt;use npm itself as a build tool&lt;/a&gt;, and since npm is a dependency anyway, we might as well make use of it. Previously, the build task was carried out by a 30-line &lt;code&gt;gulpfile.js&lt;/code&gt; and required Gulp, two plugins, and the tools that actually provided the functionality. With npm, this was reduced to a dependency on UglifyJS and a one-line command in &lt;code&gt;package.json&lt;/code&gt;. If we wanted to add a file watcher that would be one more dependency and one more command. Easy.&lt;/p&gt;&lt;p&gt;The new, rewritten version is actually &lt;a href=&quot;https://permortensen.com/games&quot;&gt;live right now&lt;/a&gt;. Nothing at all should change from the looks of it, but everything behind the scenes has been ripped out and replaced with new, shiny, (relatively) typesafe code. This is a testament to how well a single-page app with a REST API separates frontend and backend concerns: not one line of client-side Javascript has been changed as a result of this rewrite.&lt;/p&gt;&lt;p&gt;So with this, it looks like I can finally retire Apache (and maybe even MySQL), and I have learned a bunch about the .NET Core stack at the same time. I’m gonna go ahead and say that’s a win-win.&lt;/p&gt;</content>
  </entry>
  
  <entry>
    <title>A quick note about PIA port forwarding</title>
    <link href="https://permortensen.com/a-quick-note-about-pia-port-forwarding/"/>
    <updated>2016-10-20T17:06:01Z</updated>
    <id>https://permortensen.com/a-quick-note-about-pia-port-forwarding/</id>
    <author>
      <name>Per Mortensen</name>
    </author>
    <content type="html">&lt;p&gt;I use the Private Internet Access VPN provider, which allows you to request forwarding of a single port from the public IP to your machine. It’s a cool feature that technically allows you to run any kind of server application through an anonymized connection, though its primary use case is obviously to make your torrent client directly connectable.&lt;/p&gt;&lt;p&gt;But I just couldn’t get it to work. The PIA client claimed to have a port forward set up, but no traffic came through when testing it; any request I made just timed out. I exchanged about 15 emails with their support team, who gave no helpful answers to my questions and repeatedly sent me their µTorrent guide, screaming &lt;em&gt;THIS FEATURE IS FOR TORRENTING ONLY AND WE DON’T KNOW ANYTHING ABOUT LISTENING SOCKETS&lt;/em&gt;. But after a while I found a quirk that is apparently documented nowhere:&lt;/p&gt;&lt;p&gt;&lt;strong&gt;If you run your server and client application from the same machine, port forwarding won’t work.&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;It turns out the feature had (probably) been working all along, but I was testing it by listening and issuing requests from the same machine, through the VPN, to its own public IP. As soon as I used another machine as the client, everything worked as it should.&lt;/p&gt;&lt;p&gt;I don’t know if this is just an issue with my network, a bug in PIA or my own stupidity acting up, but it’s an entirely non-obvious detail that I currently can’t explain. For good measure, I asked PIA support if this was a known limitation and they responded with this:&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;Port forwarding is only used in torrenting to speed up slower downloads. Any use of port forwarding outside of that is not supported nor recommended.&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;Yeah, thanks.&lt;/p&gt;</content>
  </entry>
  
  <entry>
    <title>The rise and fall of a minor YouTube group</title>
    <link href="https://permortensen.com/the-rise-and-fall-of-a-minor-youtube-group/"/>
    <updated>2016-06-18T22:56:14Z</updated>
    <id>https://permortensen.com/the-rise-and-fall-of-a-minor-youtube-group/</id>
    <author>
      <name>Per Mortensen</name>
    </author>
    <content type="html">&lt;p&gt;If you’ve got even a passing interest in obscure internet drama, here’s a story for you. Early last month, THAC TV, the group behind the kind of popular YouTube series &lt;em&gt;&lt;a href=&quot;https://www.youtube.com/user/MarbleHornets&quot;&gt;Marble Hornets&lt;/a&gt;&lt;/em&gt; disbanded after a series of unfortunate events during the past year or so. Let me tell you a bit about it from my perspective as nothing more than a fan. Well, at least I used to be.&lt;/p&gt;&lt;h2&gt;Marble Hornets&lt;/h2&gt;&lt;p&gt;The story begins almost seven years ago. In June 2009, the Marble Hornets channel uploaded the first two videos of what would become THAC’s most successful work to date and probably — with what happened recently — of all time. The series was a fictional, found footage style horror/mystery show focusing on the internet-created character Slender Man. Originally, the series was mainly produced by two twenty-something friends, Troy Wagner and Joseph DeLage, with Troy playing the protagonist Jay and Joseph playing the (eventual) antagonist Alex. During the first season of the show, other characters were introduced, and the character of Alex’ friend Tim, played by Tim Sutton, became a mainstay and much later a fellow protagonist. Marble Hornets had a number of twists and turns in the course of its three seasons and 87 main episodes, which lasted from June 20, 2009 to June 20, 2014. In addition to the main episodes, it included &lt;a href=&quot;https://www.youtube.com/user/totheark&quot;&gt;a companion channel&lt;/a&gt; and &lt;a href=&quot;https://twitter.com/marblehornets&quot;&gt;a Twitter account&lt;/a&gt; to make it somewhat ARG-like. It was very exciting at the time, but one thing is for certain: it all went downhill from the beginning.&lt;/p&gt;&lt;p&gt;Regardless of how you feel about the individual seasons, there’s no denying that the first one is the most popular, and depending on whether or not you agree with me, it’s also the best. Marble Hornets grew from a sequence of short and unsettling clips with little context to a genuine story with characters, dialogue and drama. And these things tend to work better if they’re not written, directed and acted out by amateurs. This is by no means a dig at Troy, Joseph and Tim. No one expects world class writing and acting from a free YouTube series, but the original format was definitely better at working around these limitations, to the show’s advantage.&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://permortensen.com/assets/images/dist/F3KIT4x6JF-1619.png&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://permortensen.com/assets/images/dist/F3KIT4x6JF-800.webp 800w, https://permortensen.com/assets/images/dist/F3KIT4x6JF-1200.webp 1200w, https://permortensen.com/assets/images/dist/F3KIT4x6JF-1619.webp 1619w&quot; sizes=&quot;(max-width: 65ch) 100vw, 65ch&quot;&gt;&lt;img src=&quot;https://permortensen.com/assets/images/dist/F3KIT4x6JF-800.png&quot; alt=&quot;Views per video as of May 26, 2016&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1619&quot; height=&quot;795&quot; srcset=&quot;https://permortensen.com/assets/images/dist/F3KIT4x6JF-800.png 800w, https://permortensen.com/assets/images/dist/F3KIT4x6JF-1200.png 1200w, https://permortensen.com/assets/images/dist/F3KIT4x6JF-1619.png 1619w&quot; sizes=&quot;(max-width: 65ch) 100vw, 65ch&quot;&gt;&lt;/picture&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;At the time of writing, nearly every season 1 episode has gained over a million views, and the first few have several. Season 2 episodes hover between half a million and a million, and season 3 a few hundred thousand less.&lt;/p&gt;&lt;p&gt;As a side project to Marble Hornets, the three main creative forces behind it occasionally made surreal comedy videos for Troy’s personal YouTube channel, &lt;a href=&quot;https://www.youtube.com/user/troyhasacamera&quot;&gt;Troy Has A Camera&lt;/a&gt;. Some of these went on to be mildly successful, no doubt fueled primarily by fans of their horror series. In December 2013, during the third and final season of Marble Hornets, the group registered a company, THAC LLC, mainly for the purpose of merchandise, including &lt;a href=&quot;https://www.kickstarter.com/projects/troywagner/marble-hornets-season-3-dvd&quot;&gt;a successful Kickstarter for the season 3 DVD&lt;/a&gt;.&lt;/p&gt;&lt;h2&gt;A new direction&lt;/h2&gt;&lt;p&gt;When Marble Hornets ended for good in 2014, the group shifted their focus to comedy videos for the THAC channel, which was now their primary output. As part of this, they brought in a fourth member called Noah Scammon. I was skeptical of him at the time. With their videos and behind-the-scenes info, the three original members had built up a relationship with the fanbase. Noah hadn’t and yet became a prominent part of the face of THAC; it was like he was forced onto us. But the addition of Noah wasn’t the only noticeable change. Around this time, a pattern was emerging: give each member of the group their own recurring comedic character and show. The roster eventually turned into:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;em&gt;Nature Break&lt;/em&gt; with Craig Digsby (Troy), a nature show&lt;/li&gt;&lt;li&gt;&lt;em&gt;Space Time Express&lt;/em&gt; with Morningstar Sinclair (Tim), a space show&lt;/li&gt;&lt;li&gt;&lt;em&gt;Fixin’ a Fixit&lt;/em&gt; with Owen Hankinson (Joseph), a DIY show&lt;/li&gt;&lt;li&gt;&lt;em&gt;Spooky Drawer&lt;/em&gt; with Dominic Darkwalter (Noah), a spooky story show&lt;/li&gt;&lt;li&gt;&lt;em&gt;Cyber Tricks&lt;/em&gt; with Computer Jym (Noah), a computer show&lt;/li&gt;&lt;li&gt;&lt;em&gt;Isn’t That Something&lt;/em&gt; with Dr. Thompson Hacke (Troy), a science show&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;In addition to these formats, THAC was now also producing a variety of out-of-character videos like Let’s Plays, a podcast, and DnD campaigns. Although they would still occasionally make the kind of original shorts they started out with, it was no longer their main concern. Still, at the time, I was optimistic. Noah started to feel like an integrated part of the team, and the regular formats were occasionally entertaining. Things could definitely have been worse, and I was still somewhat invested. But as we’ll see, most of the shows would end up shortlived.&lt;/p&gt;&lt;p&gt;On August 3, 2015, new videos started appearing on the Marble Hornets channel, titled &lt;a href=&quot;https://www.youtube.com/watch?v=zkZDs8CBDkk&amp;amp;list=PL0yMAae98Fld6AFtauKsbNUctljnMhWXV&quot;&gt;&lt;em&gt;Clear Lakes 44&lt;/em&gt;&lt;/a&gt; (which have recently been unlisted). This was to be a successor to Marble Hornets — direct or spiritual — having been teased as the final stretch goal on the season 3 DVD Kickstarter. Clear Lakes 44 started out with fast updates and decent popularity, but just like with Marble Hornets, interest was steadily dropping.&lt;/p&gt;&lt;h2&gt;Halving the team&lt;/h2&gt;&lt;p&gt;Some time in October of 2015, Joseph and Tim left the group. At the time, the circumstances of the breakup were kept vague by all of the people involved. No one knew exactly how nasty the split had been. Maybe it was creative differences. Maybe it was &lt;em&gt;gigantic&lt;/em&gt; creative differences. I think it’s fair to say that the fan community was unsure how to deal with it: Troy was the founder and initial force that put THAC on the map, but Joseph and Tim had been essential parts of everything good they had ever made. Soon, Troy &lt;a href=&quot;http://web.archive.org/web/20160610065526/http://forums.thac.tv/forum/main-forum/thac-channel/121962-a-letter-from-us&quot;&gt;wrote a post&lt;/a&gt; just a few buzzwords short of being a corporate press release.&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;THAC is absolutely not a studio that has any interest to scale back or drop off content. In fact, moving forward from here, and into 2016, the company lives strong and will be releasing even more videos and series as planned.&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;According to Troy, the workforce of THAC was effectively halved, yet the workload would actually increase. A bold promise that didn’t exactly build confidence, especially from a post where the group’s usual transparency and honesty was nowhere to be found. Regardless, life — and THAC’s remaining video series — went on.&lt;/p&gt;&lt;p&gt;While Clear Lakes 44 was indeed still in production under Troy and Noah, things weren’t looking too good for it. The release schedule slowed to a crawl, fans generally found it to be too slow and confusing, and view counts were consistently lower than even the worst MH episode. The most recent CL44 episode to date, &lt;em&gt;Broadcast #13&lt;/em&gt;, was released on February 18 and has just over 39,000 views to date. I’m not exactly sure when, but somewhere along the way I stopped watching. In my opinion, Clear Lakes 44 was just too cryptic for its own good. A slow burn can be great, but as it turns out, running a web series without any significant events in its first six months may not be ideal. At the same time, THAC’s comedy videos became less and less entertaining, culminating with a bunch of mediocre Let’s Plays and &lt;a href=&quot;https://www.youtube.com/watch?v=3_taG-aYWk0&quot;&gt;this cringe material&lt;/a&gt;.&lt;/p&gt;&lt;h2&gt;Back to square one&lt;/h2&gt;&lt;p&gt;The story ends in April of this year, where &lt;a href=&quot;http://web.archive.org/web/20160606003629/http://forums.thac.tv/forum/news-and-updates/159236-why-tim-and-joseph-left-thac&quot;&gt;Troy spills the beans&lt;/a&gt; about the split with Joseph and Tim. There’s no reason for me to go into it as his account of things is very detailed and has been verified by Joseph and Tim. What I will say is this: what shred of optimism I had left for THAC is now dead and gone. I had always assumed the breakup was just a matter of creative differences, but it was much more complicated than that; things were apparently bad enough that they didn’t speak to each other for five months. Later in the thread, Noah posts a &lt;a href=&quot;http://web.archive.org/web/20170224195323/http://forums.thac.tv/forum/news-and-updates/159236-why-tim-and-joseph-left-thac/page8#post159521&quot;&gt;long sentimental post&lt;/a&gt; about his memories of the other guys, and then Joseph answers with just this:&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;Dude, eat shit. Eat shit and be glad that I still (mostly) maintain a sense of professionalism about this whole thing.&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;At first Noah’s post seems like an attempt to douse the fire, but the jarring difference between its sugar-sweet tone and Joseph’s aggressive response definitely raises a red flag. Was Noah secretly mocking him? Does Joseph hold Noah responsible for splitting the group? Is Noah just unreasonably optimistic about the whole thing? We’ll probably never know, but it’s obvious that the two aren’t exactly on good terms.&lt;/p&gt;&lt;p&gt;Two days later, after a lot of reactions, discussions and (ugh) memes, Troy &lt;a href=&quot;http://web.archive.org/web/20170306194444/http://forums.thac.tv/forum/news-and-updates/160446-thac-update&quot;&gt;posts another update&lt;/a&gt; on how he has asked Noah to leave THAC, effectively cancelling all shows he was involved in, including Clear Lakes 44. This puts THAC back to how it was seven years go: just Troy and a camera. You might think &lt;em&gt;hey, that worked out pretty well last time!&lt;/em&gt; And you’d be right. Except last time, things only got really good with the help of Joseph and Tim. Troy might be able to find other partners, sure, but there’s another problem. Last time — and this is purely speculative — it was a hobby fuelled by the passion for creating stuff. This time, it needs to make money: Troy &lt;a href=&quot;http://jtroyw.blogspot.com/2014/08/time-to-mosey-on.html&quot;&gt;has a young daughter&lt;/a&gt; he needs to support financially, and presumably has too little spare time to make THAC a hobby project.&lt;/p&gt;&lt;p&gt;My best guess: THAC, and the things it used to make, are dead. According to Troy, he’s got plans to reboot CL44 (hence the unlisting of existing episodes), but I’m not going to get my hopes up. As for the rest of the crew: Tim made a &lt;a href=&quot;https://www.youtube.com/watch?v=s9xoKJYDLVg&quot;&gt;decent video&lt;/a&gt; in February, promising more videos the following month (which have yet to surface). Joseph works at a deli and &lt;a href=&quot;http://www.twitlonger.com/show/n_1soib2n&quot;&gt;has no current plans&lt;/a&gt; to make more YouTube stuff. Noah is working as a production manager and generally has an &lt;a href=&quot;https://twitter.com/Lefthandedsocky/status/730523206466023424&quot;&gt;active online presence&lt;/a&gt;, though he hasn’t produced any significant solo content lately.&lt;/p&gt;&lt;p&gt;But let’s not end this on doom and gloom. Although I think the chances of anything worthwhile coming out of this debacle are slim at best, who knows? Maybe there will be a Clear Lakes 44 v2.0, and maybe it will be fantastic. Maybe each former member will make great videos on their own, and maybe they’ll eventually reconcile. Let’s just hope and wait.&lt;/p&gt;&lt;p&gt;&lt;em&gt;Update: Troy has rebranded his new solo outfit as &lt;a href=&quot;https://www.youtube.com/channel/UCOkx_7jkvkNGEzQcRlEmdeQ&quot;&gt;Fish With A Nose&lt;/a&gt;, which currently has no uploads and is not included in the first page of a Google search for “fish with a nose”. Best of luck to Troy!&lt;/em&gt;&lt;/p&gt;&lt;p&gt;&lt;em&gt;Update 2: The THAC forums have been discontinued, so I have updated the links to archived versions of the posts.&lt;/em&gt;&lt;/p&gt;&lt;!--
In case the archived links go down, here are the important posts:

Why Tim and Joseph left THAC
04-08-2016, 09:29 PM
A few of you may have noticed a bit of silence around here the past few days, which I apologize for. There&#39;s a reason for that though. If you&#39;re not intimidated by this wall of text, I encourage you to read on.

A few days ago, I called Tim and Joseph. It was the first time I had spoken to them in about 5 months. The main reason I did was to apologize for what ultimately lead to their departing THAC, and to begin repairing a friendship that I foolishly mixed with business too much.

Before I get into this long story, I want to quickly mention that both Joseph and Tim have read it in its entirety, and have personally approved of its accuracy.

Alright. Let’s start wayyy back. When Joseph, Tim, and I formed THAC LLC as a legitimate company, legally speaking, I was the sole owner. This was originally meant to be temporary. The company had to be formed quickly because we were in the process of getting the Marble Hornets DVDs back from Kirill Baru, and wanted to be as legitimate as possible when we began to sell them ourselves. Forming an actual company that would sell the DVDs, as well as other merchandise, was the best way to do this.

After some negotiating, we got the DVDs. I set up the original storefront (which wasn’t very good), and we set out on our new and exciting business venture. Things were going pretty alright.

During this time, the fact that I was the sole owner was brought up on several occasions. We all agreed that we should look into getting them on the books, and that we needed to research how to do that and get it done. But for some reason or another, we’d forget to do it. This will be very important a bit later.
After a while of doing pretty well for ourselves (finishing Marble Hornets, having a hugely successful Kickstarter, launching the Season 3 DVDs), we started talking about possibly getting another person on board to help us make more content. It was shortly after that that we began speaking to Noah. Originally, we only wanted him to help us create Dogdog since he had an animation background. But after meeting a few times, and pitching different ideas, we decided to make him a full time member of THAC. Again, things were going pretty alright.

However, a bit after Noah had been hired, a sense of tension between all of us began to grow, for a multitude of reasons. Unsurprisingly, a lot of it had to do with money. I was starting to feel like I was underpaid for having to spend so much time editing things we shot. Tim and Joseph, on the other hand, were uncomfortable with the amount of power that I had still being the sole owner of the company.
Viewcounts for THAC and Marble Hornets soon began to drop. This made our paychecks begin to
shrink as well. Things got more stressful for all of us as we tried to figure out what we may be doing wrong. We had to rebalance our shares of profits to better be able to live off the money equally. This led to me having to take a pay cut, which didn’t help my feeling underpaid at all. I ended up agreeing to it though because I didn’t want to seem greedy.

But the tension was still there. After a few months, Joseph and Tim had expressed numerous times that they didn’t want Noah to work with us anymore. They believed very strongly that he did not mesh with what we wanted THAC to be. I saw where they were coming from, and thought it was a reasonable thing to consider, but that we hadn’t given him a fair chance.

A few weeks pass, and the tension exploded. We had just gotten back from a convention, and Joseph and Tim brought up again that we seriously consider letting Noah go.

During the con we had just gotten back from, I had hung out with Noah and developed a stronger friendship with him. Because of that, I didn’t want to entertain the idea of getting rid of him anymore, but the way we had agreed to tackle problems like this was together, and majority rules. I would be outnumbered if they wanted to put it to a vote.

I felt that letting Noah go was a mistake. This ended up confusing Joseph and Tim, since I had previously seemed open to the idea. So to keep him around, I employed a questionable tactic. I said “hypothetically” that if it came down to it and Tim and Joseph wouldn’t budge on the matter, I’m the owner of the company and I ultimately have the last say on things. As you might imagine, Joseph and Tim weren’t too thrilled about this.
Ultimately, after Joseph and I had a heated argued about it, we cooled off and explained it away as “tone not working through text” (we were chatting via skype at the time) and moved on. However, that was the beginning of the end for Joseph and Tim, as it and shook their faith in the company in a way that wouldn’t be able to be repaired.

As the viewcount and payments began to drop even more, things were starting to look grim. My wife, daughter, and I were in the process of moving from Alabama to Georgia in order to be closer to family. This is when I found out the downside to being the sole owner of the company. Maybe it was Karma for how I had used that power previously.

We were looking at apartments and had applied to quite a few, sure that we’d get one of them. Unfortunately, one by one, they all called me and told me that I didn’t meet the requirement for income. Because I was self employed, the accepted proof of income for all the apartments was my tax return (in lieu of a pay stub). When you’re the sole owner of a company, you file your taxes in the same forms as your personal income. Because of this, to make a long story short, my taxes showed that I made negative money over the course of a year, which caused my wife and I to be rejected by every apartment we applied for. Despite still being able to afford it, it couldn’t be proven on paper.

I was getting desperate. My wife and I had no choice but to sleep on couches at her sister’s house because we had to move out of our apartment in Alabama already. We continued to apply to apartments, all the while THAC was making less and less. I wasn’t able to look for part time employment at the time because I wasn’t sure where we would end up living. Things were no longer going alright.

After about a month, my wife and I finally found a place that would accept us as tenants. It was then that I realized that I would not be able to afford all of the deposit, as it was quite a bit higher due to the proof of income issue.

Up until this point, I had never once used company money on anything besides paying Joseph, Tim, and Noah, as well as small equipment purchases that we would agree on beforehand. Now, I felt like there would be no other chance to get a place to live, and I didn’t want our infant daughter not having a true home anymore, so I made the decision to use the business debit card to pay the deposit and treat it as a loan, with the full intention of paying it back as soon as I was back on my feet. I didn’t tell Joseph, Tim, or Noah about it before I did it because the offer was very short notice. I should have.
THAC was no longer making a livable amount of money for any of us.There was no money left over at the end of the month for me to pay back my company for the loan I had given myself out of desperation.

Shortly after, at one of the rare times all four of us were in the same room, Joseph told us all that he wanted to step back creatively from THAC in order to take on other, behind the scenes duties. One of these duties was overseeing the bank account. As soon as we all agreed we were fine with him taking on these things, I immediately came clean about what I had to do with the company money. Thankfully, Joseph, Tim, and Noah were understanding and all came to the same conclusion that as long as it didn’t happen again, and it was paid back, they were okay.

I finally had a place to live, and started looking for a part time job to make ends meet and to pay back the money. Throughout all of this, we were still working on THAC and getting Clear Lakes 44 started with mixed results. Clear Lakes suffered the most because there was a fairly large number of creative differences between all of us on what the followup to Marble Hornets should be. These differences kept going even by the time Joseph and Tim left. We were never fully on board as a whole, which caused the beginning of CL44 to be a stumble instead of the big bang that we wanted.
I started to get very depressed. Things were falling apart, my friendships were strained, and we were getting paid very small amounts for a substantial amount of work.

Now for when things got really bad. At this point we had all taken a pay cut, of which mine was the largest. Joseph suggested it as an “equal pay for equal work” deal in order to get the company finances in check, which made sense. The problem was that I still didn’t feel like we were doing an equal amount of work. On the other side, Tim didn’t feel like he was being given enough.

I had briefly spoken to Joseph about feeling underpaid for all of the editing I was doing, and that if I was to pay back what I had spent, I’d need to renegotiate my share. By my reasoning, if I was putting in the most time but getting paid the same, then my time must be worth a lot less. This did not make me feel good at all. I was getting very frustrated and angry, and ended up taking it out on my friends.

I decided that I was going to rework our shares on my own. “It’s MY company after all” I told myself. I spent a few nights running numbers and trying to figure out how to rebalance things for what felt like the hundredth time. I decided that the fairest way to do it was for us to be paid for what we did every time that we did it. So for example, all of us show up to shoot a THAC short, we all get paid a flat amount for that. It made sense to me, but I ended up making another big mistake with it.

I didn’t want a shouting match to erupt between us when I brought it forward, so I decided that I wouldn’t tell any of the others about what I wanted others to be paid. So if Joseph asked me what I was offering Tim, I wouldn’t tell him, and so on. I figured they were free to share the amounts with each other after I told them, but that left out one key person. Myself.

The exact amounts don’t matter, but for the important sake of transparency, I did have it to where I was paid the most by about 15%. I had not paid back the loan yet, so wanted to use that extra amount to start doing so.

The first person I brought it up to was Joseph. Unsurprisingly, he was not happy with the fact that I was keeping my compensation under wraps. He told me that he wouldn’t budge on this until I made what I was being paid public to everyone in THAC. I didn’t want to do that and so told Joseph I wouldn’t budge on it either.

Strangely, this made me feel kind of good. I was finally putting my foot down on what I felt I deserved and not budging on it at all. In retrospect, this is probably what most people would call a power trip. Tim told me he was leaving shortly after.

I got paranoid. I had not heard from Joseph since Tim told me he was leaving the company. What if they were plotting something behind my back? I realized that they both had company cards. What if they were going to go to the bank to empty out all the money and then vanish? What if they changed the password to the THAC store out of spite? What if they deleted all of our videos? I quickly went in and changed the passwords that we shared. Youtube, the store, patreon, everything.

Of course neither of them ended up doing any of these things I imagined. At the time, Joseph was in charge of many important behind the scenes things, like shipping out store orders, keeping an eye of finances, and contacting potential conventions (I didn’t think this was very much work at the time, but have since changed my mind after having to do it myself). So when he tried to check in on the store do that, only to find that his password no longer worked, he asked me what the hell was going on.

I told him that I had changed the passwords for everything in case he and Tim try “some kind of scorched earth thing”, but that I’d change the store back for when he needed to ship things out. Joseph said he was leaving THAC as well shortly thereafter.

Afterwards, Joseph and Tim made their public statement about their departure, and I made mine. By the time I spoke about it on the THAC podcast, I had cooled down quite a bit, and wished them well as sincerely as I could.

I have since paid back the loan I gave myself by paying the total amount of it split between Joseph, Tim, and Noah equally.

During my recent phone call with Joseph and Tim, I told them about everything that led up to these events on my end, and offered a full apology for everything that happened that was caused by me.


04-09-2016, 02:10 PM
Hi everyone. Sorry I have been absent from the conversation here. that&#39;s not from a lack of interest or a method of hiding from it. I honestly wasn&#39;t informed when this was going up to be frank. Troy and I discussed what was happening and I understood his interest of being open about everything. I never really wanted anything hidden from you guys regardless. So this is a positive. With that being said, I&#39;ll do my best to address things as best I can more and more as time goes on AND want to continue to chat with all of you. For now though, I will give a semi short version because this moment is the exact moment I found out that this was posted now, and Want to let everyone know where I&#39;m at and share some thoughts too.

All I have ever wanted is to make some great content for you guys. Something funny, something strange or bizarre, something enjoyable. I felt like THAC has accomplished that and I’m very proud of that fact. Nothing is and was more rewarding than the team finishing a video, hoping it was well received and having someone very nice at a convention come up to us and say &quot;oh that one was my favorite!&quot; You guys all have been so sweet to me and to the work we&#39;ve done and I will always cherish that forever. With that being said, I don&#39;t plan on stopping anytime soon. My future is looking very bright, and I have PLENTY more to share for you all! (Both THAC and other future projects I now have the chance to focus on.) I made sure to give THAC a lot of my time over the past year, from writing and planning from home and on set, and even with taking a greyhound bus for 3-6 hours at a time (depending traffic or wait time for connecting buses), every week for around 5 months straight. It was a lot to take on, constant travel to get to THAC meetings and a lot of nights staying up to make sure if, the next boss in WOAQ was funny enough, or if we had a plan of attack to tackle the next video or not, and so on. I cared a lot about making something fun for everyone. both audience, and us working on it....and it was fun. The four of us even back then, we had good times together. I remember laughing, I remember actually having a great time every time we sat down for WOAQ, podcasts, even small film shoots. We found a way to make things fun. I will always remember that side of everything. Of course I can safely say, after all of everything, after all the tension and confusion of how things went? I still appreciate my time with everyone.I stand by my words before that I hold no grudges here against anyone. Business is business, and concepts and challenges come up. Its never fun when money creeps its ugly head. but, I know that we we made and what we fought for was worth doing, big pay out or not. Making something fun was always worth doing.

With that said, 3 short things:

1) To Joseph: Those trips to your house were long my friend, no doubt about that. I had quite the headache plenty of times having to lug everything back and forth. (Not to mention my neck feeling screwed up sleeping on a bus all those times.) But you were always willing to open your home to me to stay with you. You made it as comfortable as you could, and you were willing to make sure I had a place to sleep during production. That, will be something I will never forget. So thank you, for making me feel welcome then. (I will also never forget our times playing smash brothers, or watching that terrible hulk hogan movie together. Little things like that still ring in my head as “I remember some great times with some pretty rad people.”

2) To Tim: Every chance we got when we were alone, Tim and I had another interesting conversation. Whether it was film, quality in how things are going in the world, music (and trust me, we had PLENTY of conversations about music) . Every single one of them was a rad time. I loved sharing songs and just bouncing back and forth way something had a sound to not just hear, but to almost experience. It was a lot of fun! We were always running around filming something but time seemed to slow down when talking to tim. I liked it. Even our lunch breaks were talking work or filming or conventions. But just me and Tim, out on a porch, or nerding out how different crazy sounds can be made from such weird machines, and checking out his instruments... felt like something different. It was nice.and I fondly remember that time spent, thanks Tim, for always keeping things interesting,.

3) To Troy: Thank you, for standing up for me. You saw my intentions were honest and to only help, not hurt or take away from things with THAC. You saw the potential of all of us working together and making great things. You made things worth fighting for and you made each project more fun than the last when we had hands on time together working. You saw a future with THAC and you enjoyed working together. I still do as well. I’m proud to be on your team my friend. I look forward to everything that is to come.

And with that said, I plan to continue making projects with THAC. This isn’t goodbye at all. More will be coming from us in the near future. I do have plenty of new ideas, and still have the passion for what we started with already established series as well. WOAQ is FAR from over too! (I wrote that story gosh darnit, we’re gonna finish it so I can write more!) and many more things to come!

I cant wait to share all that is going on my my works and path (BIG things are coming! can&#39;t wait to share) , as well as more THAC content down the road too. Keep checking my social media pages for more updates and whatnot. Ill be sure to post more too. I do plan on continuing doing streams and going back to a regular schedule WILL be possible VERY soon! I appreciate everyone who has been messaging me about wanting it back, and I give a special wink and a hug your way too. :P

Lastly I just want to say. Thank you. Thank you all for being so kind, so welcoming, and so rad during my time here with THAC. I will stand by my words and continue to do my best to make a lot more things for you all and to update as soon as I can!

Love you guys &lt;3

-Noah


Thac update
04-10-2016, 07:25 PM
Hey friends,

I’ve always tried to be as transparent as possible with how things are going around here, as you might have noticed with my wall of text about Joseph and Tim’s departure, so I think it’s only fair that I keep everyone up to date.

I have respectfully requested that Noah depart THAC TV, to which he has agreed. As such, all video series that involve him (Computer Jym, Spooky Drawer, Bomblurs), will be ceasing production. I wish him the best, as I did with Joseph and Tim, and will be following and supporting whatever he wishes to do in the future. We’re still friends, as well.

Since Noah has departed, Clear Lakes 44, as it exists right now, is ceasing production too. I say “as it exists right now” because it’s not being cancelled completely. I will be rebooting and relaunching it as a new series, written and produced by me. I will leave the current version of CL44 up on the mh channel for a few weeks before setting them to unlisted and reuploading them to a separate channel. I will post links to them on the THAC Forums once that happens.

After thinking on it for quite a while, I’m not confident that the current story of CL44 would live up to the high bar that Joseph, Tim, and I were able to set with Marble Hornets. It’s always difficult and disappointing to take a step back and be able to admit when something is not working for you, and this is definitely not a decision I took lightly. I think starting over and getting back to what the original idea was and working from there in a different direction will ultimately lead to a better experience throughout the life of the series. It will take some time, but I will keep you updated on the entire process.

There is also the matter of my own mental health. I’ve been dealing with a great amount of mental fatigue, stress, and depression caused by the sheer amount of work it is to get out at least one video per week. Add a part time job on top of all that, and it’s a lot of stuff for one person to deal with. So with that in mind, I&#39;m talking a break from posting videos for a while.

Ever since my daughter was born I’ve done everything with her best interests at heart. Working on all of the stuff above, however, sometimes felt like it was interfering with my ability to be an attentive dad. I don’t want to feel that anymore. I hope you understand.

The Patreon is still active, but is now changing to a “per video” payment model, as opposed to the current monthly flat amount. This means the tiers will be shifting downwards in price since there’s a possibility that patrons can be charged more than once a month (you can still set a maximum total amount you want to spend each month). I will be taking a week or so off in order to decompress and spend time with my wife and daughter. After that, expect updates on everything I’m doing through Patreon.

As I’ve said in the past, if even one person still watches things that I make, I will keep it up. I’m so happy and thankful for the community that has so generously given me support, honest feedback, and infectious enthusiasm for things that not only I make, but the things I helped to make alongside my friends.

Things may slow down for a bit, but I promise I’m not going anywhere.

With much love,
Troy
--&gt;</content>
  </entry>
  
  <entry>
    <title>The many themes of Undertale</title>
    <link href="https://permortensen.com/the-many-themes-of-undertale/"/>
    <updated>2015-11-01T21:47:06Z</updated>
    <id>https://permortensen.com/the-many-themes-of-undertale/</id>
    <author>
      <name>Per Mortensen</name>
    </author>
    <content type="html">&lt;p&gt;If it wasn’t already obvious from its &lt;a href=&quot;http://www.metacritic.com/game/pc/undertale&quot;&gt;94 metascore&lt;/a&gt;, &lt;em&gt;Undertale&lt;/em&gt; is a pretty good game. Not that I was easy to persuade into buying it. Visually, it looks absolutely horrendous. &lt;em&gt;But that’s part of its charm,&lt;/em&gt; you say. Absolutely, but not to the uninitiated random person scrolling through new releases on Steam. It’s good to see it’s getting recognition left and right, though everyone agrees that the less you know about it beforehand the better. Go buy an indie game that looks like it belongs on the GameBoy Color without looking up what it’s about? That’s a pretty risky proposition if you ask me.&lt;/p&gt;&lt;p&gt;As of a few days ago, I’ve done a neutral run and a pacifist run. They were emotional rollercoasters and yeah yeah we all know what’s up. And now I can’t show the game to anyone or start another run because doing a true reset would break my heart after hearing the begging and knowing about the persistence tricks it pulls on you. The game is a rare example of how well meta can be done: it’s funny, it’s clever, and it’s essential to the story.&lt;/p&gt;&lt;p&gt;The soundtrack is stylistically all over the place with 8-bit, 16-bit and 32-bit instrumentation mixed together, but totally amazing all the same. I don’t think there’s a single decidedly bad song on it, which is an impressive feat for a hundred tracks. What’s fun to notice while listening to it is how certain themes are reused in creative ways throughout the entire album. And so I decided to visualize it.&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://permortensen.com/assets/images/2015/11/undertale.svg&quot;&gt;&lt;img src=&quot;https://permortensen.com/assets/images/2015/11/undertale.svg&quot; alt=&quot;Graph&quot;&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;In this directed graph (&lt;a href=&quot;https://static.per.computer/undertale.dot&quot;&gt;dot&lt;/a&gt;, &lt;a href=&quot;https://permortensen.com/assets/images/2015/11/undertale.svg&quot;&gt;svg&lt;/a&gt;) of the 101 tracks on the official soundtrack, each edge is a relation between tracks such that samples of the track with the outgoing edge are being reused or remixed in the track with the incoming edge. When determining which track should be the source and which should be the sink, the album ordering is used: the earlier the track appears on the official tracklist, the more “original” it is considered.&lt;/p&gt;&lt;p&gt;I created this purely by listening to the entire soundtrack and picking up on similarities, so while the majority is covered, I probably missed some connections here and there.&lt;/p&gt;&lt;p&gt;Edges that I deemed redundant have been removed. A good example of this is the relationship between &lt;em&gt;Once Upon a Time&lt;/em&gt;, &lt;em&gt;Start Menu&lt;/em&gt; and &lt;em&gt;Menu (Full)&lt;/em&gt;. The third is mostly a re-instrumentation of the second, which in turn owes its melody to the first. In my original graph, there was an additional edge between the first and third, but this (and others like it) have been removed to make things easier to read.&lt;/p&gt;&lt;p&gt;The most obvious recurring theme is &lt;em&gt;Once Upon a Time&lt;/em&gt;. It’s the first theme you hear when starting the game, and it’s used throughout the entire game right up until the end. &lt;em&gt;Bring It In, Guys!&lt;/em&gt; is a credits medley of various themes, which is why it has a lot of incoming edges.&lt;/p&gt;&lt;p&gt;I have no deeper analysis to go along with the graph. Just &lt;a href=&quot;https://tobyfox.bandcamp.com/album/undertale-soundtrack&quot;&gt;have a listen to the soundtrack&lt;/a&gt; and pick out the moments where the themes merge and evolve :)&lt;/p&gt;</content>
  </entry>
  
  <entry>
    <title>It&#39;s April again...</title>
    <link href="https://permortensen.com/its-april-again/"/>
    <updated>2015-04-08T14:12:40Z</updated>
    <id>https://permortensen.com/its-april-again/</id>
    <author>
      <name>Per Mortensen</name>
    </author>
    <content type="html">&lt;p&gt;… and my cake gains another candle.&lt;/p&gt;&lt;p&gt;I can’t believe how some people come up with material for daily or even just weekly blog posts, god damn. Since my last awkward attempt at updating this thing, I have resigned from my (now previous) job and started another, and on top of that I’ve become involved in a side project that’s very ambitious for two guys to be developing in their spare time. In addition to my education of course, which my parents keep reminding me not to neglect. Don’t worry, mom and dad, I won’t. Too badly.&lt;/p&gt;&lt;p&gt;Gaming-wise, it’s actually been a slow year so far. Just for curiosity’s sake, I’ve got a text file where I try to place as many games as I can onto a when-did-I-play-this timeline (which I might integrate into my &lt;a href=&quot;http://permortensen.com/games&quot;&gt;collection app&lt;/a&gt; at some point). Maybe it’s just because remembering what discs I was putting into my PlayStation a decade ago is hard, but there’s a pretty clear trend going on:&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://permortensen.com/assets/images/dist/GfcLQJmd1S-472.png&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://permortensen.com/assets/images/dist/GfcLQJmd1S-472.webp 472w&quot; sizes=&quot;(max-width: 65ch) 100vw, 65ch&quot;&gt;&lt;img src=&quot;https://permortensen.com/assets/images/dist/GfcLQJmd1S-472.png&quot; alt=&quot;chart&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;472&quot; height=&quot;243&quot;&gt;&lt;/picture&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;You know, I feel slightly embarrassed. Being a half-casual gamer is a dubious claim when you finish 50 games in a year; that’s roughly one each week. And it’s not even socially acceptable games like &lt;em&gt;FIFA&lt;/em&gt; or &lt;em&gt;Halo&lt;/em&gt; where playing the game doesn’t automatically place a giant L on your forehead in the eyes of society. It’s more like &lt;em&gt;BioShock&lt;/em&gt; and &lt;em&gt;Skyrim&lt;/em&gt;. “I just play it to pass the time.” Yeah right.&lt;/p&gt;&lt;p&gt;If only the internet didn’t exist so I could be blissfully unaware of new releases and not just rush through everything. With the internet gone, maybe I’d even gain some social skills and meet new people. Oh well, a guy can dream. Or have nightmares. It’s both, really.&lt;/p&gt;&lt;p&gt;But it looks like 2015 is bucking the trend. So far I’ve played three games to the credits: &lt;em&gt;Far Cry 4&lt;/em&gt;, &lt;em&gt;Alien: Isolation&lt;/em&gt; and &lt;em&gt;Wolfenstein: The New Order&lt;/em&gt;, and I’m currently a few cases into &lt;em&gt;L.A. Noire&lt;/em&gt;. That’s more like one a month.&lt;/p&gt;&lt;p&gt;It’s alright. As I said there are plenty of things to do and so gaming time is deprioritized, as it should, and I consciously try to avoid turning a fun hobby into a chore by rushing things.&lt;/p&gt;&lt;p&gt;Of course, I’ll have to play &lt;em&gt;GTA V&lt;/em&gt; when the PC version comes out in a week, and I’ve also been hearing a lot of praise for &lt;em&gt;Pillars of Eternity&lt;/em&gt;, which several of my friends are playing. Maybe I can finally get into something that can’t be played with a controller. Maybe I’ll also suddenly become a cool dude and accomplish something with my life.&lt;/p&gt;&lt;p&gt;Yeah right.&lt;/p&gt;</content>
  </entry>
  
  <entry>
    <title>Long time no write</title>
    <link href="https://permortensen.com/long-time-no-write/"/>
    <updated>2015-01-31T11:07:36Z</updated>
    <id>https://permortensen.com/long-time-no-write/</id>
    <author>
      <name>Per Mortensen</name>
    </author>
    <content type="html">&lt;p&gt;&lt;a href=&quot;https://permortensen.com/assets/images/dist/1e12c2cMkJ-824.png&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://permortensen.com/assets/images/dist/1e12c2cMkJ-800.webp 800w, https://permortensen.com/assets/images/dist/1e12c2cMkJ-824.webp 824w&quot; sizes=&quot;(max-width: 65ch) 100vw, 65ch&quot;&gt;&lt;img src=&quot;https://permortensen.com/assets/images/dist/1e12c2cMkJ-800.png&quot; alt=&quot;cleanliness&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;824&quot; height=&quot;624&quot; srcset=&quot;https://permortensen.com/assets/images/dist/1e12c2cMkJ-800.png 800w, https://permortensen.com/assets/images/dist/1e12c2cMkJ-824.png 824w&quot; sizes=&quot;(max-width: 65ch) 100vw, 65ch&quot;&gt;&lt;/picture&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;My my, how time flies. It’s been a busy couple of months since my last post with school and work running at high capacity. There has still been time to play some games, but nothing I really want to talk about, so here’s a one-sentence summary of each:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;em&gt;Alice: Madness Returns&lt;/em&gt;: I haven’t seen quadruple jumps since … actually I don’t remember ever seeing quadruple jumps before.&lt;/li&gt;&lt;li&gt;&lt;em&gt;Oddworld: Stranger’s Wrath&lt;/em&gt;: A pretty cool twist.&lt;/li&gt;&lt;li&gt;&lt;em&gt;Torchlight 2&lt;/em&gt;: Fooled ya! That’s not the final boss!&lt;/li&gt;&lt;li&gt;&lt;em&gt;Shadow Warrior&lt;/em&gt;: 17 chapters with enough ideas for maybe 8.&lt;/li&gt;&lt;li&gt;&lt;em&gt;Darksiders 2&lt;/em&gt;: Look boss I made the level huge so there’s room for all the players … wait what do you mean it’s not an MMO?&lt;/li&gt;&lt;li&gt;&lt;em&gt;Viscera Cleanup Detail&lt;/em&gt;: Whoops you brushed the bucket against my arm and now I’m dead.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Now that’s taken care of, here are some other things. I want to say “things not about video games” but of course it fucking is.&lt;/p&gt;&lt;p&gt;For one of my school projects, we created &lt;em&gt;Monospace Hero&lt;/em&gt;, which is part game, part level building tool and allows you to write your own platforming levels using only a text editor. Basically, you’re a symbol jumping around on other symbols. Check &lt;a href=&quot;https://permortensen.com/projects/monospace-hero/&quot;&gt;this page&lt;/a&gt; for more details.&lt;/p&gt;&lt;p&gt;For a few years I’ve been using my own web based video game collection app to keep track of my growing collection across several platforms and services. I still feel that things like Playfire, while great in many aspects, do not give me enough control over my data, which is really the most important feature to me. So for the time being I’m relying on my own custom solution.&lt;/p&gt;&lt;p&gt;A year ago, the app was just a for-each loop in PHP spitting out all the rows in a database table. Today, that’s still a part of it, but the entire thing has been rewritten as an AngularJS application using a JSON API and Bootstrap. It provides advanced filtering, high customizability, statistics, and a whole bunch of other features. I even wrote an installer that makes it about as easy to install as WordPress! I now call it &lt;em&gt;g_&lt;/em&gt;. I’m still working on getting it a nice description page (and probably publishing the source somewhere), but for now, my actual live version that I use can be found &lt;a href=&quot;http://permortensen.com/games&quot;&gt;right here&lt;/a&gt;.&lt;/p&gt;</content>
  </entry>
  
  <entry>
    <title>I completed a hard game for once</title>
    <link href="https://permortensen.com/i-completed-a-hard-game-for-once/"/>
    <updated>2014-11-20T21:06:47Z</updated>
    <id>https://permortensen.com/i-completed-a-hard-game-for-once/</id>
    <author>
      <name>Per Mortensen</name>
    </author>
    <content type="html">&lt;p&gt;Being kind of a quitter, I’m very easy to dissuade from pretty much anything and I find comfort in the fact that if life gets too hard, there’s always an easy way out called suicide. So while I’m waiting for that to inevitably happen, hard games and I have an agreement. I don’t play them, and in return they don’t make me feel like an idiot. Sometimes though, like a zoogoer jumping the fence to the lion den, I break the rules and get mauled and eaten. Figuratively speaking. Almost.&lt;/p&gt;&lt;p&gt;High difficulty is why I never finished &lt;em&gt;Super Meat Boy&lt;/em&gt;. It’s why I only finished &lt;em&gt;XCOM: Enemy Unknown&lt;/em&gt; through save scumming, and it’s why I’m surprised I managed to finish &lt;em&gt;Dark Souls&lt;/em&gt;. Originally I was going to use this post to complain how the game was basically unplayable, but then something happened and I was actually enjoying it.&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://permortensen.com/assets/images/dist/1A9Q6W22Cb-734.png&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://permortensen.com/assets/images/dist/1A9Q6W22Cb-734.webp 734w&quot; sizes=&quot;(max-width: 65ch) 100vw, 65ch&quot;&gt;&lt;img src=&quot;https://permortensen.com/assets/images/dist/1A9Q6W22Cb-734.png&quot; alt=&quot;This fucking thing&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;734&quot; height=&quot;652&quot;&gt;&lt;/picture&gt;&lt;/a&gt;&lt;em&gt;Also, this fucking thing&lt;/em&gt;&lt;/p&gt;&lt;p&gt;At first, I went in blind with a melee class and got stuck in Undead Burg. Then, I started a cleric game, cheated every stat to 99 and still died at the bridge to Undead Parish. It drove me half mad. As I &lt;a href=&quot;https://permortensen.com/immersion/&quot;&gt;previously mentioned&lt;/a&gt;, I have trouble keeping interested in games nowadays, and an unusually high difficulty certainly doesn’t help. I hate leaving a game half finished — it should preferably be either finished or left alone from the start — but playing the same thing over and over without progressing is even worse, and that’s exactly what &lt;em&gt;Dark Souls&lt;/em&gt; was making me do. Oh, but it’s alright, you see, you’re &lt;em&gt;supposed&lt;/em&gt; to play it for it’s difficulty, which is a bit like saying you should visit Africa for the ebola.&lt;/p&gt;&lt;p&gt;I had just about had it with the game when I found the &lt;a href=&quot;https://www.youtube.com/playlist?list=PLefxJHc3SH3sJ8rd_etFsYh22sOveB7bV&quot;&gt;&lt;em&gt;Dark Souls&lt;/em&gt; easy mode walkthrough&lt;/a&gt; by some guy calling himself the Tech Phantom. Despite the name and the intro (it gets old), his walkthrough is very in-depth and for the most part noob-friendly. The reason for making it, he says, is to demonstrate that an easier difficulty setting for &lt;em&gt;Dark Souls&lt;/em&gt; is unnecessary, but that doesn’t really work when the entire thing hinges on taking very specific routes to get very specific items, i.e. things you’d need expert-level knowledge of the game to know is even there. And new players aren’t going to have expert-level knowledge, they’re going to flail their starting mace around and hope that whatever’s in front of them ends up dying before they do (it won’t).&lt;/p&gt;&lt;p&gt;I shouldn’t be too hard on the guy, though. While I would have loved to say that I completed the game through perseverance and carefully honed skills, the truth is that I have one person to thank for it, and that’s the… “Tech Phantom.”&lt;/p&gt;&lt;p&gt;Now that I actually finished it, I find it hard to formulate a concise opinion about it. It’s an immensely obtuse game that starts out with saying &lt;em&gt;“ring two bells, one in a church and one underground, GL HF ;)”&lt;/em&gt; and you’re basically left to figure out for yourself which way will get you killed by a gang of skeletons and which way will get you killed by a gang of ghosts, which can be really jarring if you’re coming from any game with objective markers and extensive tutorials. I’d say this approach lends itself very well to exploration, which it does, but the consequences of going the wrong way are grave enough that exploration is only fun when you have learnt to play the game well.&lt;/p&gt;&lt;p&gt;All in all though, it sucked me in as soon as I was able to make steady progress, and it kept me interested for a lot longer than many other games have. The open world design is probably it’s strongest point — everything is connected in ways that’ll make you go &lt;em&gt;oooh!&lt;/em&gt; — but there’s just so much shit you have to go through to appreciate it, and that makes it a hard sell.&lt;/p&gt;&lt;p&gt;Heh. Hard. Get it?&lt;/p&gt;</content>
  </entry>
  
  <entry>
    <title>Golden Suns</title>
    <link href="https://permortensen.com/golden-suns/"/>
    <updated>2014-09-17T14:09:56Z</updated>
    <id>https://permortensen.com/golden-suns/</id>
    <author>
      <name>Per Mortensen</name>
    </author>
    <content type="html">&lt;p&gt;I never really cared much for handheld gaming. My commutes have rarely been long enough to justify gaming sessions, and if I’m at home I’d much rather enjoy something with a proper controller and a large screen.&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://permortensen.com/assets/images/dist/aFN3l2nBMz-240.png&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://permortensen.com/assets/images/dist/aFN3l2nBMz-240.webp 240w&quot; sizes=&quot;(max-width: 65ch) 100vw, 65ch&quot;&gt;&lt;img src=&quot;https://permortensen.com/assets/images/dist/aFN3l2nBMz-240.png&quot; alt=&quot;goldensun&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;240&quot; height=&quot;160&quot;&gt;&lt;/picture&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;I do have a soft spot for personal recommendations though, and in this case, a friend of mine recommended &lt;em&gt;Golden Sun&lt;/em&gt; to me: an FF-esque turn-based RPG for the Game Boy Advance. I’ve never owned a Nintendo machine in my life, but thanks to the &lt;a href=&quot;https://play.google.com/store/apps/details?id=com.fastemulator.gbafree&quot;&gt;wonders of emulation&lt;/a&gt; it is now possible to play this thing on any decent smartphone. Yeah, using Visual Boy Advance on your PC is totally a thing but for obvious reasons it doesn’t really capture the feel of handhelds (notably eyestrain and finger cramps). While emulation is never completely authentic, My Boy! works really well for the most part, enough so that I left a 5 star review of it on the Play Store. I wouldn’t want to play &lt;em&gt;Super Mario&lt;/em&gt; on it though, I’ll tell you that.&lt;/p&gt;&lt;p&gt;Anyway, &lt;em&gt;Golden Sun&lt;/em&gt;. My friend repeatedly tells me how great these games are, and while I initially fought his rose-tinted glasses with huge grains of salt, it turns out he’s kind of right. At first glance, it seems like a totally generic JRPG. It has random encounters, an inventory system, turn-based battles that take place in an alternate reality, and protagonists who look about twelve. Where it begins to veer off familiar turf is when it introduces its djinn system, a twist on summons. Basically, each djinni (summon) can be either “set” on a party member to boost their stats, or unset, at which point the djinni will use its unique ability. It can then be used as a simple elemental summon attack or be combined with other unset djinn to form stronger summons.&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://permortensen.com/assets/images/dist/DKHNuzSLgA-240.png&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://permortensen.com/assets/images/dist/DKHNuzSLgA-240.webp 240w&quot; sizes=&quot;(max-width: 65ch) 100vw, 65ch&quot;&gt;&lt;img src=&quot;https://permortensen.com/assets/images/dist/DKHNuzSLgA-240.png&quot; alt=&quot;Golden Sun - The Lost Age_1409510906388&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;240&quot; height=&quot;160&quot;&gt;&lt;/picture&gt;&lt;/a&gt;&lt;em&gt;Sometimes, my strategy fails&lt;/em&gt;&lt;/p&gt;&lt;p&gt;It’s a pretty neat system. Do you keep each djinni set to boost your stats or do you need something provided by their abilities? Do you summon them right away to regain your stat boost or do you unset all your djinn for a stronger summon? How much of your stat boost can you afford to swap for greater summon damage? There’s a lot of room to experiment and strategize. You know, if you don’t just spam &lt;em&gt;Attack&lt;/em&gt; over and over like I do.&lt;/p&gt;&lt;p&gt;Another cool feature: you know how in most JRPGs your spells don’t do shit outside battles? In &lt;em&gt;Golden Sun&lt;/em&gt;, every dungeon and town has some environment puzzles that can only be solved using specific spells, like moving a pillar out of the way, freezing a puddle of water into a platform, or growing a plant into a climbable vine. It’s not just some gimmick either; it’s used so often that it becomes a central game element, and it works well. Sadly, it’s not always as intuitive as you’d want. In the sequel, &lt;em&gt;The Lost Age&lt;/em&gt;, you get several spells for overcoming rock obstacles — burst, grind, and lift — that are very similar but only work on their respective kind of rock, which the game just assumes you’re cool with. The obvious reason is gating players until they reach a certain point in the story, but it seems to me they could have easily used other obstacles with somewhat clearer requirements.&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://permortensen.com/assets/images/dist/kUXl3xz2YP-240.png&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://permortensen.com/assets/images/dist/kUXl3xz2YP-240.webp 240w&quot; sizes=&quot;(max-width: 65ch) 100vw, 65ch&quot;&gt;&lt;img src=&quot;https://permortensen.com/assets/images/dist/kUXl3xz2YP-240.png&quot; alt=&quot;Golden Sun - The Lost Age_1406298156506&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;240&quot; height=&quot;160&quot;&gt;&lt;/picture&gt;&lt;/a&gt;&lt;em&gt;It’s true&lt;/em&gt;&lt;/p&gt;&lt;p&gt;In any case, I thoroughly enjoyed the games for most of the 30 or so hours I spent on each, and I would recommend checking them out. What’s kind of admirable about &lt;em&gt;The Lost Age&lt;/em&gt; is that they didn’t try to reinvent the gameplay significantly, they just took the original and made everything bigger and more impressive. Instead of two continents to explore, you’ve now got five continents and a bunch of smaller islands. Instead of walking everywhere, you now get a ship that also gets wings and the ability to fly. It’s like they wanted to make the coolest motherfucking ship ever, and they probably succeeded. I can tell you right now that if I had played it as a kid, I would be absolutely ecstatic about a flying boat.&lt;/p&gt;&lt;p&gt;More than I am now, I mean.&lt;/p&gt;</content>
  </entry>
  
  <entry>
    <title>Look at canines: #000000 flag</title>
    <link href="https://permortensen.com/look-at-canines-000000-flag/"/>
    <updated>2014-08-03T16:55:23Z</updated>
    <id>https://permortensen.com/look-at-canines-000000-flag/</id>
    <author>
      <name>Per Mortensen</name>
    </author>
    <content type="html">&lt;p&gt;&lt;s&gt;This&lt;/s&gt; last month’s post is running late and I’ve come up with no real theme to talk about, so here’s a few scattered thoughts on some games I played recently, as if anyone actually gives a shit.&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://permortensen.com/assets/images/dist/kW926yMqQP-1252.jpeg&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://permortensen.com/assets/images/dist/kW926yMqQP-800.webp 800w, https://permortensen.com/assets/images/dist/kW926yMqQP-1200.webp 1200w, https://permortensen.com/assets/images/dist/kW926yMqQP-1252.webp 1252w&quot; sizes=&quot;(max-width: 65ch) 100vw, 65ch&quot;&gt;&lt;img src=&quot;https://permortensen.com/assets/images/dist/kW926yMqQP-800.jpeg&quot; alt=&quot;ac4&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1252&quot; height=&quot;1080&quot; srcset=&quot;https://permortensen.com/assets/images/dist/kW926yMqQP-800.jpeg 800w, https://permortensen.com/assets/images/dist/kW926yMqQP-1200.jpeg 1200w, https://permortensen.com/assets/images/dist/kW926yMqQP-1252.jpeg 1252w&quot; sizes=&quot;(max-width: 65ch) 100vw, 65ch&quot;&gt;&lt;/picture&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&lt;em&gt;Assassin’s Creed IV&lt;/em&gt; surprised me. I was a fan of &lt;em&gt;AC&lt;/em&gt; from the first up to &lt;em&gt;Revelations&lt;/em&gt;, but then &lt;em&gt;III&lt;/em&gt; turned out to be kind of shit and I basically lost any hope for the series. Along came &lt;em&gt;IV&lt;/em&gt;, where they took the few things that worked in the previous one — like tree climbing, human shields, and sailing — and polished them so they became something you actually wanted to do. Pretty much everything in the game works really well and I don’t think I’ve ever been this fond of an &lt;em&gt;Assassin’s Creed&lt;/em&gt; game before.&lt;/p&gt;&lt;p&gt;What’s more is the refreshing take on the protagonist. He’s not even an assassin, he just stole the clothes off one and bluffed his way through a bunch of tutorials. I also found the outside-Animus world with its clever jabs through the fourth wall much more interesting than the Desmond Miles story. I’ve always felt that the whole Animus deal was unnecessary — why not just be an assassin rather than a simulation of one? — but now we’re kinda stuck with it so anything that shifts the story focus to the part of the game everyone came to play is a plus in my book.&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://permortensen.com/assets/images/dist/WX5D7u6O1o-773.jpeg&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://permortensen.com/assets/images/dist/WX5D7u6O1o-773.webp 773w&quot; sizes=&quot;(max-width: 65ch) 100vw, 65ch&quot;&gt;&lt;img src=&quot;https://permortensen.com/assets/images/dist/WX5D7u6O1o-773.jpeg&quot; alt=&quot;watchdogs&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;773&quot; height=&quot;899&quot;&gt;&lt;/picture&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;As for &lt;em&gt;Watch Dogs&lt;/em&gt;, I initially thought strategic hacking would be the only viable way to progress, but it’s really more of an additional tool in your toolbox. At its core, the game is just a pretty good shooter/stealth game that also just happens to contain hacking. I had hoped for a hacking mechanic that entailed more than a contextual button press, but I guess it’s hard to make the Unix terminal exciting and I guess what we got is good enough. For a first try, I mean.&lt;/p&gt;&lt;p&gt;I like how, in classic video game logic, you can carry around all the weapons you’ve found, but it never gels with the rest of the game trying to be a Hollywood movie. There’s one cutscene where Aiden is forced to hand over his weapons and he gives the guy one measly handgun. The only thing I could think of was the truckload of assault rifles and shotguns I had been lugging around for the past few hours. Considering he passed the subsequent metal detector scan, the guy could probably make a pretty good living as an arms smuggler.&lt;/p&gt;&lt;p&gt;&lt;em&gt;Enslaved: Odyssey to the West&lt;/em&gt; was better than I thought it would be. Gameplay is nothing to write home about, but the charming protagonists and colorful world are what sets it apart from being just average. I thought the facial animations were pretty spectacular, and they really helped bring some life to the characters. What a strange ending though. It looks like the game was nearing release and Ninja Theory went &lt;em&gt;“fuck it, just put Andy in front of the camera and have him explain.”&lt;/em&gt;&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://permortensen.com/assets/images/dist/6LmZyAA60M-701.jpeg&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://permortensen.com/assets/images/dist/6LmZyAA60M-701.webp 701w&quot; sizes=&quot;(max-width: 65ch) 100vw, 65ch&quot;&gt;&lt;img src=&quot;https://permortensen.com/assets/images/dist/6LmZyAA60M-701.jpeg&quot; alt=&quot;miasmata&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;701&quot; height=&quot;1050&quot;&gt;&lt;/picture&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;I enjoyed &lt;em&gt;Miasmata&lt;/em&gt;. Let it be known that there are problems with many aspects of the game, but one absolutely stand-out feature is the exploration. Finding your way around the island and mapping it out yourself is a really solid concept and I do wish more games would use something like it. I found the panther-cow-goat thing creepy the first few times but then it became more of a temporary nuisance than an actual threat, and I don’t think the game necessarily needed it.&lt;/p&gt;&lt;p&gt;One of my tensest moments was when I got lost in the middle of the woods at night and couldn’t find any known landmarks. The tension undoubtedly came from the knowledge that at any time, the creature could appear and kill me, costing me valuable progress, but had the fever and thirst been fatal, it would have the same effect. The game needs &lt;a href=&quot;https://permortensen.com/horror-without-consequence-isnt-really-horror/&quot;&gt;some sort of danger to provide consequence&lt;/a&gt;, but whether that comes from an aggressive creature or your own body turning against you is less important.&lt;/p&gt;&lt;p&gt;The bundle that included &lt;em&gt;Miasmata&lt;/em&gt; also gave me &lt;em&gt;Spate&lt;/em&gt;, which is one of those games few people ever hear about. After completing it in just short of 90 minutes I found out it was &lt;a href=&quot;https://www.kickstarter.com/projects/379113968/spate&quot;&gt;Kickstarter-funded&lt;/a&gt; with what looks like a fairly limited budget in this day and age. The game turned out alright, I guess. There isn’t much to it apart from some gorgeously weird set pieces, though I have to admit I also liked the tall robot dude who just seemed like a really nice guy. And ironically this next statement is coming from someone who is himself demonstrably bad at writing, but I do think the script could have used some work here and there (&lt;em&gt;“having that moment of regret that hits a man right before the ground hits him”&lt;/em&gt; is an obvious candidate).&lt;/p&gt;&lt;p&gt;So I guess that’s a recap of the last couple of months. I’ve been spending lots of time with &lt;em&gt;Golden Sun: The Lost Age&lt;/em&gt; as well. Hopefully I’ll get to complete it someday.&lt;/p&gt;</content>
  </entry>
  
  <entry>
    <title>Horror without consequence isn&#39;t really horror</title>
    <link href="https://permortensen.com/horror-without-consequence-isnt-really-horror/"/>
    <updated>2014-06-17T17:22:13Z</updated>
    <id>https://permortensen.com/horror-without-consequence-isnt-really-horror/</id>
    <author>
      <name>Per Mortensen</name>
    </author>
    <content type="html">&lt;p&gt;&lt;a href=&quot;https://permortensen.com/assets/images/dist/CZ938RHPY5-1497.png&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://permortensen.com/assets/images/dist/CZ938RHPY5-800.webp 800w, https://permortensen.com/assets/images/dist/CZ938RHPY5-1200.webp 1200w, https://permortensen.com/assets/images/dist/CZ938RHPY5-1497.webp 1497w&quot; sizes=&quot;(max-width: 65ch) 100vw, 65ch&quot;&gt;&lt;img src=&quot;https://permortensen.com/assets/images/dist/CZ938RHPY5-800.png&quot; alt=&quot;outlastdr&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1497&quot; height=&quot;1080&quot; srcset=&quot;https://permortensen.com/assets/images/dist/CZ938RHPY5-800.png 800w, https://permortensen.com/assets/images/dist/CZ938RHPY5-1200.png 1200w, https://permortensen.com/assets/images/dist/CZ938RHPY5-1497.png 1497w&quot; sizes=&quot;(max-width: 65ch) 100vw, 65ch&quot;&gt;&lt;/picture&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&lt;em&gt;Outlast&lt;/em&gt; is a pretty good game. The last chapter is kind of a letdown because the Walrider isn’t particularly frightening, but other than that the atmosphere is usually well crafted, and the infrared camera mechanic really adds to the experience. If there’s one thing you’ll see more than anything else though, it’s &lt;em&gt;Saving …&lt;/em&gt; in the corner of the screen.&lt;/p&gt;&lt;p&gt;I’m not one of those people who despise checkpoints in general. In fact, I find frequent checkpoints an important feature because I for one don’t want to replay the entire level ten times before nailing it. Playing the same thing over and over gets real frustrating real fast, especially in games where you might suddenly be blown to bits by a grenade you had no chance of noticing. But this comes with a great big caveat: it severely diminishes any tension the game is trying to build up.&lt;/p&gt;&lt;p&gt;As with all horror games, the moment you figure out its patterns and limitations is the moment you stop fearing it. I remember how I was scared shitless the first time I played the original &lt;em&gt;Slender&lt;/em&gt;, but with a few tricks — in particular, slow and calculated movements — it naturally lost much of its tension. This applies to &lt;em&gt;Outlast&lt;/em&gt; as well; once you figure out that you can just hide in the same room again and again without the enemies getting any smarter, any actual fright is effectively destroyed.&lt;/p&gt;&lt;p&gt;Add to that the frequent checkpoints where dying robs you of very little progress and you realize that the best strategy might actually be to carelessly explore the level, reload and know exactly where to go and where to hide. That can’t be good for a genre that pretty much lives and dies by its ability to keep you on the edge of your seat. At least with &lt;em&gt;Slender&lt;/em&gt;, you had to start over if you were caught, so that actually carried with it some consequence.&lt;/p&gt;&lt;p&gt;Don’t get me wrong, &lt;em&gt;Outlast&lt;/em&gt; is still a good game, and I think one simple tweak could help the horror along: just don’t show the checkpoint notification. Without it, you wouldn’t know how far back dying would take you. While you’d probably still figure it out eventually, it would be less obvious from the get-go. A more involved tweak would be reducing the checkpoint frequency by half, but then you’re walking a fine line between tension and frustration, especially once the difficulty ramps up towards the end.&lt;/p&gt;</content>
  </entry>
  
  <entry>
    <title>Immersion</title>
    <link href="https://permortensen.com/immersion/"/>
    <updated>2014-05-18T20:43:12Z</updated>
    <id>https://permortensen.com/immersion/</id>
    <author>
      <name>Per Mortensen</name>
    </author>
    <content type="html">&lt;p&gt;Whenever people ask me &lt;em&gt;“Per, what is the most important aspect of an RPG?”&lt;/em&gt; I always answer &lt;em&gt;“The game world”&lt;/em&gt;. No one ever asks me that, so I usually tell them preemptively. The most important part of an RPG is its world. There you go.&lt;/p&gt;&lt;p&gt;I recently tried out the very awkwardly named &lt;em&gt;Monster Hunter Portable 3rd&lt;/em&gt; with two buddies and while they were going on about armor sets and crafting ingredients, I was mostly wondering how interesting the environments would be. As it turns out, not very, as each map is basically a bunch of small rooms linked together by portals. I rarely give a shit about stats and combat intricacies, so I was disappointed. I guess I’m a fucking casual.&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://permortensen.com/assets/images/dist/V4waXfS_pd-471.png&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://permortensen.com/assets/images/dist/V4waXfS_pd-471.webp 471w&quot; sizes=&quot;(max-width: 65ch) 100vw, 65ch&quot;&gt;&lt;img src=&quot;https://permortensen.com/assets/images/dist/V4waXfS_pd-471.png&quot; alt=&quot;gw2&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;471&quot; height=&quot;669&quot;&gt;&lt;/picture&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;Anyway, that made me think. If there’s anything emotionally meaningful that video games will probably always have over movies — you know, except for being fun and all that — it’s the ability to immerse. I enjoy movies as much as the next guy but when all is said and done it’s usually two hours of predetermined events, however well written or visually spectacular, in a world that doesn’t really exist beyond those two hours. This is true for a lot of games as well, but with RPGs in particular, you’ve often got a much more tangible world that doesn’t simply stop existing once the credits roll.&lt;/p&gt;&lt;p&gt;But you know what most certainly doesn’t add to the immersion? Receiving a Steam IM about cute kittens on YouTube. I speak from experience.&lt;/p&gt;&lt;p&gt;I’m one of those people who like to be reachable at any given time. Whenever my desktop PC is on (which is pretty much whenever I’m home) I’m online on Steam and Skype, and thanks to my phone I’m always reachable on Facebook, via e-mail, texts, or through a plain old phone call. Ah, the wonders of instant and ubiquitous communication. Where it gets annoying is when you’re in the middle of something else and the chat notification basically drags you feet first out into the harsh reality of expectations and responsibilities.&lt;/p&gt;&lt;p&gt;Back when I was younger I could spend hundreds of hours investigating every nook and cranny of each game. I realize this is very much &lt;em&gt;#firstworldproblems&lt;/em&gt;, but obtaining a new game was a rare occurrence and you basically had to make do with what you had. I have the benefit of age and wisdom on my side (did I mention I’m in my early twenties?) to realize that wasn’t necessarily a bad thing. The fewer games you have in your backlog, the more you appreciate what you do have, and the less you’re inclined to just rush through it to the next one. And playing it at a slower pace makes it much easier for the immersion to kick in, even for a mediocre game. That’s my experience, anyway.&lt;/p&gt;&lt;p&gt;So the next time there’s a $1 Humble Bundle, maybe I should ask myself: do I really &lt;em&gt;want&lt;/em&gt; to play these games? And the next time there’s an evening off for gaming, maybe I’ll just turn off everything but the game and give it my undivided attention for a few hours. Maybe. If I don’t end up slitting my wrists from communication withdrawal. We’ll see.&lt;/p&gt;</content>
  </entry>
</feed>