<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.3.2">Jekyll</generator><link href="https://push.cx/feed.xml" rel="self" type="application/atom+xml" /><link href="https://push.cx/" rel="alternate" type="text/html" /><updated>2024-01-27T12:31:28-06:00</updated><id>https://push.cx/feed.xml</id><title type="html">Push.cx</title><subtitle>Blog of [Peter Bhat Harkins](https://malaprop.org)</subtitle><entry><title type="html">Wrapping Large-Scale Refactors</title><link href="https://push.cx/large-refactors.html" rel="alternate" type="text/html" title="Wrapping Large-Scale Refactors" /><published>2024-01-27T11:10:05-06:00</published><updated>2024-01-27T11:10:05-06:00</updated><id>https://push.cx/large-refactors</id><content type="html" xml:base="https://push.cx/large-refactors.html"><![CDATA[<p>I really liked a “<a href="https://max.engineer/long-term-refactors">Long Term Refactors</a>” by Max Chernyak explaining a nice development practice.
I was reminded of a thing that surprised me about refactors and dependency management.</p>

<p>My last job had a codebase large enough (~50M LOC) that there were always large-scale refactors in flight, which was a new experience for me.</p>

<p>One thing this article doesn’t specifically call out is that any kind of dependency update or replacement, whether an internal or external library, is a large-scale refactor.
You benefit enormously if you can do this incrementally as the author describes instead of a <a href="https://en.wikipedia.org/wiki/Flag_day_(computing)">flag day</a> or One Giant Merge to update all uses.
A counterintuitive result is that replacing one dep with another (foolib -&gt; barlib) is <em>easier</em> than updating one (foolib 1 -&gt; foolib 2)!
Most languages do not allow you to depend on multiple versions of a package and have different sections of your codebase call different versions.
Sometimes internally-maintained dependencies will rename just to get around this limitation.</p>

<p>There’s a style of managing dependencies that mandates you must wrap usage of libraries or APIs.
Rather than calling <code class="language-plaintext highlighter-rouge">Foolib::Thing.new</code>, you’ll create your own <code class="language-plaintext highlighter-rouge">FooThing</code> (maybe using the decorator or facade patterns) and that class is the only place allowed to import from or call into foolib.
With less exposure of foolib, it’s easier to create internal documentation, audit or control usage, or replace foolib with barlib.
I don’t find this a cost worth paying in smaller codebases, but easily worth it in large ones.</p>

<p>Part of why it’s worthwhile is that it gives you two new methods for dealing with dependency updates.
First (hopefully), you have a single codesite that uses foolib so a single team can make a small change to update foolib.
Or second, if there are extensive changes that mandate changes at callsites, you can rename <code class="language-plaintext highlighter-rouge">FooThing</code> to <code class="language-plaintext highlighter-rouge">FooThing1</code> (usually an easy, if large diff), introduce <code class="language-plaintext highlighter-rouge">FooThing2</code> with the new API, and then use a process like the one this article describes to make that change incrementally to the entire codebase.
Either you update foolib at the start of this process and <code class="language-plaintext highlighter-rouge">FooThing1</code> maps old usage to new, or <code class="language-plaintext highlighter-rouge">FooThing2</code> maps new usage to old and you bump foolib at the end.
This process works quite well, whether foolib is an internal or external dependency.
Whereas, say, emailing all-dev@example.com a link to the foolib release notes and dictum that on some particular date that all foolib usage must be updated will inevitably produce significant internal discord and never, ever an on-schedule completion.
An even worse and more common failure mode for internal libs is to quietly mark foolib deprecated and direct people to rewrite to barlib when they show up with urgent questions about foolib during an outage - but of course good sense and steps 6-8 of the process described in the post would avoid such an outlandish footgunning.</p>

<p>(This post was originally a <a href="https://lobste.rs/s/bi2b1j">comment on Lobste.rs</a>
but then I realized it’s a nice excuse to break the 5.5 year dry spell here.)</p>]]></content><author><name></name></author><category term="Code" /><category term="refactoring" /><category term="large codebases" /><category term="practices" /><summary type="html"><![CDATA[I really liked a “Long Term Refactors” by Max Chernyak explaining a nice development practice. I was reminded of a thing that surprised me about refactors and dependency management.]]></summary></entry><entry><title type="html">NixOS on prgmr and Failing to Learn Nix</title><link href="https://push.cx/nixos.html" rel="alternate" type="text/html" title="NixOS on prgmr and Failing to Learn Nix" /><published>2018-07-04T13:30:05-05:00</published><updated>2018-07-04T13:30:05-05:00</updated><id>https://push.cx/nixos</id><content type="html" xml:base="https://push.cx/nixos.html"><![CDATA[<p>This is a writeup of my notes on how to get <a href="https://nixos.org">NixOS</a> running on a VPS at <a href="https://prgmr.com">prgmr</a>, followed by more general notes on this experiment in learning nix.</p>

<h3 id="provision">Provision</h3>

<p>I went with the lowest tier, currently 1.25 GiB RAM, 15 GiB Disk for $5/month. I’m only running <a href="https://weechat.org">weechat</a> for irc/twitter/fediverse/slack and some miscellaneous small things. For “pre-installed distribution” I chose “None (HVM)”.</p>

<h3 id="netboot-to-start-install">Netboot to start install</h3>

<p>I ssh’d into the management console, <code class="language-plaintext highlighter-rouge">ssh </code><em><code class="language-plaintext highlighter-rouge">[hostname]</code></em><code class="language-plaintext highlighter-rouge">@</code><em><code class="language-plaintext highlighter-rouge">[hostname]</code></em><code class="language-plaintext highlighter-rouge">.console.xen.prgmr.com</code></p>

<ul>
  <li>6 bootloader</li>
  <li>4 netboot installer, pick nixos</li>
  <li>0 twice for main menu</li>
  <li>4 to power off</li>
  <li>2 to start (see “Booting” below)</li>
  <li>1 to log in as root with no password (relax, ssh is off)</li>
</ul>

<h3 id="partition">Partition</h3>

<p>Surprisingly, the included 1.25 GB of RAM was not enough to run some nix commands.
I had to back up and recreate the box with some swap space.
I didn’t think too hard about it, just guessed at 2 GB and it worked OK.
[2018-07-09: Vaibhav Sagar suggested this is probably <a href="https://github.com/NixOS/nix/issues/1681">this known bug</a>.]{.marginnote .update}</p>

<p>` gdisk /dev/xvda`{lang=”bash”}</p>

<p>` o to create gpt`{lang=”bash”}</p>

<p><code class="language-plaintext highlighter-rouge">n to create swap partition</code>{lang=”bash”}</p>

<p><code class="language-plaintext highlighter-rouge">Command (? for help): n Partition number (1-128, default 1): 1 First sector (34-31457246, default = 2048) or {+-}size{KMGTP}: Last sector (2048-31457246, default = 31457246) or {+-}size{KMGTP}: +32M Current type is 'Linux filesystem' Hex code or GUID (L to show codes, Enter = 8300): EF02 Changed type of partition to 'BIOS boot partition'</code>{lang=”bash”}</p>

<p><code class="language-plaintext highlighter-rouge">Command (? for help): n Partition number (2-128, default 2): First sector (34-31457246, default = 67584) or {+-}size{KMGTP}: Last sector (67584-31457246, default = 31457246) or {+-}size{KMGTP}: -2G Current type is 'Linux filesystem' Hex code or GUID (L to show codes, Enter = 8300): Changed type of partition to 'Linux filesystem'</code>{lang=”bash”}</p>

<p><code class="language-plaintext highlighter-rouge">Command (? for help): n Partition number (3-128, default 3): First sector (34-31457246, default = 27262976) or {+-}size{KMGTP}: Last sector (27262976-31457246, default = 31457246) or {+-}size{KMGTP}: Current type is 'Linux filesystem' Hex code or GUID (L to show codes, Enter = 8300): 8200 Changed type of partition to 'Linux swap'</code>{lang=”bash”}</p>

<p><code class="language-plaintext highlighter-rouge">w to write and exit</code>{lang=”bash”}</p>

<p><code class="language-plaintext highlighter-rouge">mkswap -L swap /dev/xvda3</code>{lang=”bash”}</p>

<p><code class="language-plaintext highlighter-rouge">swapon /dev/xvda3</code>{lang=”bash”}</p>

<p><code class="language-plaintext highlighter-rouge">mkfs.ext4 -L root /dev/xvda2</code>{lang=”bash”}</p>

<p>` <code class="language-plaintext highlighter-rouge">{lang="bash"}</code>mount /dev/xvda2 /mnt `{lang=”bash”}</p>

<h3 id="configuring-nix">Configuring nix</h3>

<p>I generated the initial config and added a few <a href="https://wiki.prgmr.com/mediawiki/index.php/NixOS">prgmr-specific tweaks</a>:</p>

<p>` nixos-generate-config –root /mnt`{lang=”bash”}</p>

<p><code class="language-plaintext highlighter-rouge">cd /mnt/etc/nixos</code>{lang=”bash”}</p>

<p>` <code class="language-plaintext highlighter-rouge">{lang="bash"}</code>vi configuration.nix `{lang=”bash”}</p>

<p>Here’s my tweaks:</p>

<p>` boot.loader.grub.device = “/dev/xvda”`{lang=”nix”}</p>

<p>` # prgmr console config: boot.loader.grub.extraConfig = “serial –unit=0 –speed=115200 ; terminal_input serial console ; terminal_output serial console”; boot.kernelParams = [“console=ttyS0”];`{lang=”nix”}</p>

<p><code class="language-plaintext highlighter-rouge">environment.systemPackages = with pkgs; [ bitlbee tmux weechat wget vim ];</code>{lang=”nix”}</p>

<p><code class="language-plaintext highlighter-rouge">services.openssh.enable = true;</code>{lang=”nix”}</p>

<p><code class="language-plaintext highlighter-rouge">networking.firewall.allowedTCPPorts = [ 22 ];</code>{lang=”nix”}</p>

<p><code class="language-plaintext highlighter-rouge">sound.enable = false; services.xserver.enable = false; services.openssh.enable = true;</code>{lang=”nix”}</p>

<p>` <code class="language-plaintext highlighter-rouge">{lang="nix"}</code> users.extraUsers.pushcx = { name = “pushcx”; isNormalUser = true; extraGroups = [ “wheel” “disk” “systemd-journal” ]; uid = 1000; openssh.authorizedKeys.keys = [ “[ssh public key here]” ]; }; `{lang=”nix”}</p>

<p>Then I ran <code class="language-plaintext highlighter-rouge">nixos-install</code> to install the system.</p>

<h3 id="booting">Booting</h3>

<p>The NixOS manual says you should be able to run <code class="language-plaintext highlighter-rouge">reboot</code> to boot to the new system, but something in xen doesn’t reload the new boot code and I got the netboot again rather than the new system.
After talking to prgmr I found it worked if I pulled up the management console and did:</p>

<ul>
  <li>6 -&gt; 1 boot from disk</li>
  <li>then 4 to fully poweroff</li>
  <li>then 2 to create/start</li>
</ul>

<p>After this I had a running system that I could ssh into as a regular user.</p>

<p><a href="https://prgmr.com">Prgmr</a> donates hosting to <a href="https://lobste.rs">Lobsters</a>,
but because <a href="https://lobste.rs/u/alynpost">Alan</a> configured the hosting, this was my first time really using the system.
It was painless and getting support in #prgmr on Freenode was comfortable for me as a longtime IRC user.
I liked them before, and now I’m happy to recommend them for no-nonsense VPS hosting.</p>

<h2 id="nixnixos">Nix/NixOS</h2>

<p>I did this setup because I’ve been meaning to learn nix (the package manager) and NixOS (the Linux distribution built on nix) for a while.
As I <a href="https://lobste.rs/s/p2b1gn/arch_linux_developer_friendly_operating#c_gda5mm">commented on Lobsters</a>, they look like they didn’t start from manual configuration and automate that, they started from thinking hard about what system configuration is and encoded that.
(The final impetus was that I ran out of stored credit at Digital Ocean, hit several billing bugs trying to pay them, and couldn’t contact support - six tries in four mediums only got roboresponses.)</p>

<p>The <a href="https://nixos.org/nixos/manual/">NixOS manual</a> is solid and I had little trouble installing the OS.
It did a great job of working through a practical installation while explaining the underlying concepts.</p>

<p>I then turned to the <a href="https://nixos.org/nix/manual">Nix manual</a> to learn more about working with and creating packages and failed, even with help from the nixos IRC channel and issue tracker.
I think the fundamental cause is that it wasn’t written for newbies to learn nix from; there’s a man-page like approach where it only makes sense if you already understand it.</p>

<p>Ultimately I was stopped because I needed to create a package for <a href="https://github.com/kensanata/bitlbee-mastodon">bitlbee-mastodon</a> and <a href="https://github.com/wee-slack/wee-slack">weeslack</a>. As is normal for a small distro, it hasn’t packaged these kind of uncommon things (or complex desktop stuff like Chrome[2018-07-16: I’ve learned that Nix <em>does</em> have a package for Chrome, but it doesn’t appear in <code class="language-plaintext highlighter-rouge">nix-env</code> searches or the <a href="https://nixos.org/nixos/packages.html#chrome">official package list</a> because it’s hidden by an option that is not referenced in system config files, user config files, the NixOS Manual, the Nix Manual, the man page for <code class="language-plaintext highlighter-rouge">nix-env</code>, the package search site, or the the documentation of any other tool it hides packages from.]{.marginnote .update}) but I got the impression the selection grows daily.
I didn’t want to install them manually (which I doubt would really work on NixOS), I wanted an exercise to learn packaging so I could package my own software and run NixOS on servers (the recent issues/PRs/commits on <a href="https://github.com/lobsters/lobsters-ansible">lobsters-ansible</a> tell the tale of my escalating frustration at its design limitations).</p>

<p>The manual’s instructions to build and package GNU’s “hello world” binary <a href="https://github.com/NixOS/nix/issues/2259">don’t actually work</a> (gory details there).
I got the strong impression that no one has ever sat down to watch a newbie work through this doc and see where they get confused; not only do fundamentals go unexplained and the samples not work, there’s no discussion of common errors.
Frustratingly, it also conflates building a package with contributing to nixpkgs, the official NixOS package repository.</p>

<p>Either this is a fundamental confusion in nix documentation or there’s some undocumented assumption about what tools go where that I never understood.
As an example, I tried to run <code class="language-plaintext highlighter-rouge">nix-shell</code> (which I think is the standard tool for debugging builds but it has expert-only docs) and it was described over in the <a href="https://nixos.org/nixpkgs/manual/">Nixpkgs Manual</a> even though it’s for all packaging issues.
To use the shell I have to understand <a href="https://nixos.org/nixpkgs/manual/#sec-stdenv-phases">“phases”</a>, but some of the ones listed simply don’t exist in the shell environment.
I can’t guess if this a bug, out-dated docs, or incomplete docs.
And that’s before I got to confusing “you just have to know it” issues like the <code class="language-plaintext highlighter-rouge">src</code> attribute becoming <code class="language-plaintext highlighter-rouge">unpackPhase</code> rather than <code class="language-plaintext highlighter-rouge">srcPhase</code>, or “learn from bitter experience” issues like <code class="language-plaintext highlighter-rouge">nix-shell</code> polluting the working directory and carrying state between build attempts.
(This is where I gave up.)</p>

<p>I don’t know how the NixOS Manual turned out so well; the rest of the docs have this fractal issue where, at every level of detail, every part of the system is incompletely or incorrectly described somewhere other than expected.
I backed up and reread the homepages and about pages to make sure I didn’t miss a tutorial or other introduction that might have helped make sense of this, but found nothing besides these manuals.
If I sound bewildered and frustrated, then I’ve accurately conveyed the experience.
I gave up trying to learn nix, even though it still looks like the only packaging/deployment system with the right perspective on the problems.</p>

<p>I’d chalk it up to nix being young, but there’s some oddities that look like legacy issues.
For example, commands vary: it’s <code class="language-plaintext highlighter-rouge">nix-env -i</code> to install a package, but <code class="language-plaintext highlighter-rouge">nix-channel</code> only has long options like <code class="language-plaintext highlighter-rouge">--add</code>, and <code class="language-plaintext highlighter-rouge">nix-rebuild switch</code> uses the more modern “subcommand” style.
With no coherent style, you have to memorize which commands use which syntax - again, one of those things newbies stumble on but experts don’t notice and may not even recognize as a problem.</p>

<p>Finally, there’s two closely-related issues in nix that look like misdesigns, or at least badly-missed opportunities.
I don’t have a lot of confidence in these because, as recounted, I was unable to learn to use nix.
Mostly these are based on my 20 years of administrating Linux systems, especially the provisioning and devops work I’ve done with Chef, Puppet, Ansible, Capistrano, and scripting that I’ve done in the last 10.
Experience has led me to think that the hard parts of deployment and provisioning boil down to a running system being like a running program making heavy use of mutable global variables (eg. the filesystem):
the pain comes from unmanaged changes and surprisingly complex moving parts.</p>

<p>The first issue is that Nix templatizes config files.
There’s an example in my <code class="language-plaintext highlighter-rouge">configuration.nix</code> notes above: rather than editing the grub config file, the system lifts copies from this config file to paste into a template of of grub’s config file that must be hidden away somewhere.
So now instead of just knowing grub’s config, you have to know it <em>plus</em> what interface the packager decided to design on top of it by reading the package source (and I had to google to find that).
There’s warts like <code class="language-plaintext highlighter-rouge">extraConfig</code> that throw up their hands at the inevitable uncaptured complexity and offer a interface to inject arbitrary text into the config.
I hope “inject” puts you in a better frame of mind than “interface”: this is <a href="http://wiki.c2.com/?StringlyTyped">stringly-typed</a> text interpolation and a typo in the value means an error from grub rather than nix.
This whole thing must be a ton of extra work for packagers, and if there’s a benefit over <code class="language-plaintext highlighter-rouge">vi /etc/default/grub</code> it’s not apparent (maybe in provisioning, though I never got to nixops).</p>

<p>This whole system is both complex and incomplete, and it would evaporate if nix configured packages by providing a default config file in a package with a command to pull it into <code class="language-plaintext highlighter-rouge">/etc/nix</code> or <code class="language-plaintext highlighter-rouge">/etc/nixos</code> for you to edit and nix to copy back into the running system when you upgrade or switch.
This would lend itself very well to keeping the system config under version control, which is never suggested in the manual and doesn’t seem to be integrated at any level of the tooling - itself a puzzling omission, given the emphasis on repeatability.</p>

<p>Second, to support this complexity, they developed their own programming language.
(My best guess - I don’t actually know which is the chicken and which is the egg.)
A nix config file isn’t data, it’s a turning-complete language with conditionals, loops, closures, scoping, etc.
Again, this must have been a ton of work to implement and a young, small-team programming language has all the obvious issues like no debugger, confusing un-googleable error messages that don’t list filenames and line numbers, etc.; and then there’s the learning costs to users.
Weirdly for a system inspired by functional programming, it’s dynamically typed, so it feels very much like the featureset and limited tooling/community of JavaScript circa 1998.
In contrast to JavaScript, the nix programming language is only used by one project, so it’s unlikely to see anything like the improvements in JS in last 20 years.
And while JavaScript would be an improvement over inventing a language, using Racket or Haskell to create a DSL would be a big improvement.</p>

<p>These are two apparent missed opportunities, not fatal flaws.
Again, I wasn’t able to learn nix to the level that I understand how and why it was designed this way, so I’m not setting forth a strongly-held opinion.
They’re really strange, expensive decisions that I don’t see a compelling reason for, and they look like they’d be difficult to change.
Probably they have already been beaten to death on a mailing list somewhere, but I’m too frustrated by how much time I’ve wasted to go looking.</p>

<p>I’ve scheduled a calendar reminder for a year from now to see if the manual improves or if <a href="https://twitter.com/lucperkins/status/999007471141240832">Luc Perkins’s book</a> is out.[2018-08-09: I wasted another two days trying Nix from the other direction.
Rather than build up from the basics I tried to start from the top down and create a “Hello World” Rails app.
It’s hard to tell around the bugs and docs, but I’m pretty sure it’s not possible to run a Rails app on NixOS.</p>

<p>2019-12-01: New attempt. Got an incorrect error message that /sbin/bash didn’t exist. Paved and reinstalled, then tried to build a <a href="https://github.com/zetavg/rails-nix-sample">Rails demo</a> but I got a useless error message when it failed to install 1password (!?). The two errors came while pasting directly from the install docs and I was told “lmk when you figure out which command you were just calling wrong”. The documentation’s <a href="https://github.com/NixOS/nix/issues/2259">first example is still broken</a> and following install steps invariably leads to errors. I’m sick of being told it’s my fault nix doesn’t work and I’m giving up on it.
]{.marginnote}</p>]]></content><author><name>{&quot;display_name&quot;=&gt;&quot;Peter Harkins&quot;, &quot;login&quot;=&gt;&quot;admin&quot;, &quot;email&quot;=&gt;&quot;ph@malaprop.org&quot;, &quot;url&quot;=&gt;&quot;http://malaprop.org&quot;}</name><email>ph@malaprop.org</email></author><category term="Code" /><category term="nix" /><category term="prgmr" /><category term="docs" /><summary type="html"><![CDATA[This is a writeup of my notes on how to get NixOS running on a VPS at prgmr, followed by more general notes on this experiment in learning nix.]]></summary></entry><entry><title type="html">House Rules</title><link href="https://push.cx/house-rules.html" rel="alternate" type="text/html" title="House Rules" /><published>2018-06-13T16:38:43-05:00</published><updated>2018-06-13T16:38:43-05:00</updated><id>https://push.cx/house-rules</id><content type="html" xml:base="https://push.cx/house-rules.html"><![CDATA[<p>I really enjoy playing board games with friends, as you can probably guess from my <a href="/media-reviews">media reviews</a>. Over the last ~25 years of playing we’ve evolved a couple house rules that are worth formalizing and sharing.</p>

<h2 id="1-yes-take-backsies">1. Yes Take-backsies</h2>

<p>We’re playing for the fun of learning new games and competing. If you make a mistake and we can unwind it, you can take it back and do what you meant to do or realize you should have done.</p>

<p>We don’t do take-backsies of things we can’t easily and fairly unwind. For example, in Risk, if you roll the dice to attack another player’s army and get wiped out, well, that’s not fair to unwind. Similarly, if you learn hidden information like turning over the next card on the deck, we can’t make everyone forget that (but mayyyybe if it’s someone’s first time playing a game).</p>

<p>Making mistakes is a normal part of learning. By helping keep them cheap and emphasizing that it’s socially rewarded to admit and correct them, everyone learns and plays better, and gets to relax and enjoy themself more. Games are fun because they’re an <a href="https://www.raphkoster.com/2018/03/16/the-trust-spectrum/">exercise in trust</a> as much as exercise in formal systems.</p>

<p>We very often have players with uneven experience and take-backsies helps the newbies get into a game and keep the moderately-experienced competitive with the experts. We encourage suggestions and polite criticism of in-progress mistakes, too. When learning a complex game it’s hard to recognize the legal moves available and what their tradeoffs might be. So when someone looks particularly stumped, it’s normal to hear something like “Sooo... it looks like you have five or maybe six things you could do here, depending on what’s in your hand?” to offer help.</p>

<p>Suggestions show up even with competitive, experienced players, in part because some of us are so competitive we <a href="https://en.wikipedia.org/wiki/Integer_overflow">wrap around</a> into helping our opponents make the best moves for their strategies so that we’re ourselves pushed into <a href="http://www.sirlin.net/ptw">improving our own play</a>. Suggestions aren’t a rule because some people don’t like hearing them and it’s more common in competitive play to want opponents to make mistakes, but I wanted to go into it because it’s foundational to the Yes Take-Backsies rule.</p>

<h2 id="2-public-stays-public">2. Public Stays Public</h2>

<p>Information that’s revealed during gameplay stays public information and can be reviewed at any time unless doing so severely inconveniences the flow of gameplay.</p>

<p>What this means in practice is cards get discarded face-up and spread out for anyone to look through, or other things that have been played can be reviewed. The limit is that we might run out of table space or it would be distinctly un-fun to dig through that many cards, but when there’s that much information laying around it’s probably not particularly important what’s been played.</p>

<p>For example, <a href="https://boardgamegeek.com/boardgame/929/great-dalmuti">The Great Dalmuti</a> is a light trick-taking card game that’s a longtime favorite. Players play cards to try to empty their hands before everyone else, and between hands they shift to sit in the order they finished in to get benefits for the next hand. The last player is punished with the chore of clearing away the cards from each trick (and they have some other chores) so we task the next-to-last player. Instead of the game rule that all played cards are flipped face-down, our house rule is to arrange the best six ranks (numbered 1-6 of the deck’s 12 ranks) face-up along the side of the table. We don’t do this for ranks 7+ to balance the work vs how little strategy is at work in high cards (&gt;85% of the time the correct play is “dump any of them at first opportunity”). This has been a big success. Beginners graduate from learning the rules to start exercising strategy in 2-3 hands instead of 5-6 hands, experts can experiment more effectively, and everyone’s happier not trying to remember “wait... did I see <em>all</em> of the threes, or just two of them?”</p>

<p>Information that’s derivable from public information is also public. In <a href="https://boardgamegeek.com/boardgame/5/acquire">Acquire</a> every player starts with the same amount of money and buys and sells stock in public transactions... but the rulebook suggests keeping it secret to make the game “even more challenging”. Arithmetic is not a fun game, it is a chore.</p>

<p>These “memory subgames” crop up all over, are stressful and uninteresting, and seem an unfair advantage for those who are better at this or spend a few days learning mnemonic techniques. When public information stays public, players make fewer uninformed and mistaken decisions.</p>

<p>The common downside is that sometimes players bog down the game while sifting through old cards or falling into <a href="https://en.wikipedia.org/wiki/Analysis_paralysis">analysis paralysis</a>. We solve this through good-natured grumbling and some smoothly-worn old jokes that reference getting on with things before the mountains crumble into the seas, etc. In rare cases [(or rare players)]{.small} we’ll set a turn timer on someone’s phone. Or give suggestions! Once you’ve looked at all the things an opponent might do and thought through how you’d respond to them and gotten bored, there’s no harm in talking to the opponent about their options.</p>

<p>To keep things moving along (especially when playing someone with very good recall of previous game state), it’s common to ask something like, “Wait, have you played all your aces?” rather than spend twenty seconds flipping through their discards. An honesty norm has developed: you can answer honestly, you can fib and say “I’m not sure” (if you think it gives you a competitive advantage and they won’t check), in an fiercely competitive game you can say “count for yourself”, but you can’t lie. If you’ve played all your aces but say you haven’t, or vice-versa, or otherwise deliberately give false information, it’s considered very rude, unsportsmanlike conduct and is treated almost as negatively as cheating. It’s OK to want to win and normal not to want to help your opponent, but we’ve established lying about public information as something that gets you a lot of frowning friends. We’re good friends or becoming them, so social disapproval means this basically never happens. (Exception: <a href="https://en.wikipedia.org/wiki/Diplomacy_(game)">Diplomacy</a> is a <a href="https://en.wikipedia.org/wiki/Blood_sport">blood sport</a>.)</p>

<p>Public Stays Public is a much younger and more explicit rule than Yes Take-backsies, which grew out of long habit. We had only very small, limited experiments with it until about six months ago (December 2017), when I heard <a href="https://push.cx/2017/attending-recurse-center">at Recurse Center</a> that someone knew of a gaming club (maybe at MIT?) that had it as a universal house rule. I decided it was worth trying and we’ve enjoyed it in every game since.</p>

<h2 id="more">More?</h2>

<p>Our process of developing house rules is a very much like developing traditions. We’re doing this slowly and sometimes only recognizing in retrospect that we have one because we learn another group doesn’t. I’m finally getting around to writing this up today because it came up in an online chat and my response kept getting longer and longer, so maybe I’ll add more in the future.</p>

<p>I suppose one thing worth noting is that in the last decade as games have generally gotten much better (and we’ve gotten more patient), we’ve become much more reluctant to add house rules to individual games. Usually something that seems weird and wrong is a corner of gameplay worth exploring and deliberating rather than something we feel we comfortable immediately trying to prune. We have almost no per-game house rules, really.</p>]]></content><author><name>{&quot;display_name&quot;=&gt;&quot;Peter Harkins&quot;, &quot;login&quot;=&gt;&quot;admin&quot;, &quot;email&quot;=&gt;&quot;ph@malaprop.org&quot;, &quot;url&quot;=&gt;&quot;http://malaprop.org&quot;}</name><email>ph@malaprop.org</email></author><category term="Games" /><category term="tabletop games" /><category term="board games" /><category term="card games" /><summary type="html"><![CDATA[I really enjoy playing board games with friends, as you can probably guess from my media reviews. Over the last ~25 years of playing we’ve evolved a couple house rules that are worth formalizing and sharing.]]></summary></entry><entry><title type="html">Return Statement</title><link href="https://push.cx/return-statement.html" rel="alternate" type="text/html" title="Return Statement" /><published>2017-12-15T15:49:21-06:00</published><updated>2017-12-15T15:49:21-06:00</updated><id>https://push.cx/return-statement</id><content type="html" xml:base="https://push.cx/return-statement.html"><![CDATA[<p>I’ve finished <a href="https://push.cx/2017/attending-recurse-center">my time at Recurse Center</a>, and a small <a href="https://www.google.com/search?q=%22return%20statement%22%20%22recurse%20center%22">tradition</a> is to write a “return statement” about what you did in your time there.</p>

<p>Attending <a href="https://www.recurse.com">Recurse Center</a> was a very rewarding time for me, personally and professionally. I succeeded at studying some difficult topics, I learned about a number of things I’d never have thought to look for, and even more important I got to spend three months with a group of curious, kind, smart, generous developers. I am both proud and humbled to be a part of the Recurse community.</p>

<p>I was a bit unusual among Recurse attendees in how narrowly I focused on studying Haskell, so most of this list of things I did is other stuff:</p>

<ul>
  <li>Finished a <a href="https://github.com/pushcx/shenzhen/">solver for Shenzen Solitaire</a>, a Haskell program to tell me how bad I should feel about my results at the game (very bad, around 99% of games are winnable). I got practical experience in Haskell modeling data, managing state, testing, property-based testing, debugging, and tooling. I’d been working on this for a few months at <a href="/code-and-coffee">Code and Coffee</a> before I started.</li>
  <li>Soldered and assmbled a <a href="https://www.youtube.com/watch?v=bEPg8kk84gw">Planck keyboard</a>, then configured it using the very nice <a href="http://qmk.fm/">QMK firmware</a> with a <a href="https://normanlayout.info/">Norman layout</a>, then trained that up to an acceptable 75wpm. I’ll write a longer post about keyboards at some point, but for anyone who gets curious to buy a kit and might buy from MassDrop, let me mention that I ended up deleting my account over how badly they mismanaged the process and how dishonestly they communicated about it.</li>
  <li><a href="https://push.cx/2017/lobsters">Became the sysop</a> of <a href="https://lobste.rs/">Lobsters</a>. This wasn’t because of Recurse, but it was a big life event that happened during my time at RC. I planned the migration, learned ansible to help administrate the new servers, and started writing Rails code again for the first time in a year to maintain and improve the site.</li>
  <li>Met with a twice-weekly Haskell study group to work through <a href="http://haskellbook.com">Haskell Programming from First Principles</a>. I was mostly ahead of the group from prior study, but still learned from all the discussion and helping others. The group split up around Thanksgiving as folks decided to focus on various personal projects.</li>
  <li>Got an introduction to <a href="http://us.metamath.org/">MetaMath</a> and had a multi-day conversation about isomorphisms between different branches of mathematics and computer science, as well as a number of related topics like the role of git in collaborative projects.</li>
  <li>Attended a reading with Q&amp;A by <a href="https://en.wikipedia.org/wiki/Ellen_Ullman">Ellen Ullman</a>, whose book “Close to the Machine” is a cornerstone in my understanding of the experience of programming and its effects on programmers and the world at large.</li>
  <li>Had an interesting experience in sleep deprivation: I started to do some system administration, but quickly recognized that I was too tired to be logged into anything as root. I changed gears to refactoring Haskell and was able to make real improvements by leaning on the type system. Usually I would’ve been writing the commits that featured in the ‘git blame`s of next week’s debugging, but when I looked back they were all solid. The maxim “if it compiles, if works” seems to be true of Haskell refactoring.</li>
  <li>Learned about <a href="https://lobste.rs/s/n5ftsy/survey_symbolic_execution_techniques">symbolic execution and concolic testing</a>, an exciting new field of research in program correctness. It’s very academic now and I want to try my hand at writing a practical tool; if it can scale to real-world code it could be tremendously valuable.</li>
  <li>Gave a 5-minute lightning talk introducing functors. I’ll probably record this and put it up on <a href="https://valent.io/talks">my talks page</a> before the new year. Came in under my usual ratio of one hour of prep time to one minute of presentation.</li>
  <li>Helped a family member’s small Wordpress hosting company recover from being defaced by an Albanian script kiddie. I miss when helping older relatives with tech meant setting VCR clocks.</li>
  <li>Spent two weeks awakened every morning at exactly 3:05 AM. Maybe a building heater had a new routine for the cold weather or something, but the NYC area is noticeably louder at night than Chicago.</li>
  <li>Attended several tech talks, most organized by RC.</li>
  <li>Visited Chicago for a wedding, which was a great chance to see friends and family after so long away.</li>
  <li>I’ve long dithered over the correct order for projects on <a href="https://malaprop.org">my homepage</a>. I cut the gordian knot by randomizing it with the rng seeded by year + week so it doesn’t change every single reload.</li>
  <li>Spent Thanksgiving vounteering to serve meals to the elderly and indigent at a restaurant. After taking orders and running food for a bit, I spent the rest of my day behind the bar serving drinks. This was a very rewarding day and I see more volunteering in my future.</li>
  <li>Restarted my use of <a href="https://www.gwern.net/Spaced-repetition">spaced repetition</a> study software (Anki + AnkiDroid) to shore up my American sign language and solidify the foundation of my Haskell knowledge as I shift into app dev. If the time I spend learning is an investment, SRS is a maintenance contract: I pay a little on a regular basis to ensure the investment doesn’t get lost.</li>
  <li>Took a sharp detour in my last week of studying. Rather than continue to the end ot the Haskell book, I worked on art project called An Inaccurate Clock. Learned SVG for it, which is a nice tool for interactive graphing and vector drawing that I’m glad to put in my toolbox. (This will probably not be released publicly.)</li>
  <li>Got to try an Oculus Rift VR headset, which I was nervous about as almost all 3D games give me motion sickness. I had a lot of fun with First Contact and Superhot and had only a mild reaction.</li>
  <li>Toured New York City a bit. I have family in the area and have visited a number of times, so I didn’t do much of this. Highlights included walking Rockaway Beach, the High Line, a picnic in Central Park, and many long walks through Manhattan and Brooklyn. Took a side trip down to Philadelphia and Baltimore (east coast cities are so close together!) to visit some old friends.</li>
  <li>Met a lot of very good people, saw their excellent, creative, strange, funny, personal, and impressive projects, and had a hundred great conversations about code, practices, careers, and life in general. (Veni, vidi, colloqui.)</li>
</ul>

<p>If you’re considering <a href="https://www.recurse.com/apply">attending</a> Recurse, it is almost certainly worth your time. I’d be happy to answer any questions you have by email, on Twitter, or on Freenode.</p>

<p>If you’re considering <a href="https://www.recurse.com/hire">hiring</a> through Recurse (they keep the lights on with recruiting fees), it is an excellent source of high-quality developers. I don’t know what stronger signal you could get than someone choosing to spend three months learning and collaborating, really.</p>]]></content><author><name>{&quot;display_name&quot;=&gt;&quot;Peter Harkins&quot;, &quot;login&quot;=&gt;&quot;admin&quot;, &quot;email&quot;=&gt;&quot;ph@malaprop.org&quot;, &quot;url&quot;=&gt;&quot;http://malaprop.org&quot;}</name><email>ph@malaprop.org</email></author><category term="Life" /><category term="Recurse Center" /><category term="New York City" /><summary type="html"><![CDATA[I’ve finished my time at Recurse Center, and a small tradition is to write a “return statement” about what you did in your time there.]]></summary></entry><entry><title type="html">Voicemail</title><link href="https://push.cx/voicemail.html" rel="alternate" type="text/html" title="Voicemail" /><published>2017-12-03T13:50:09-06:00</published><updated>2017-12-03T13:50:09-06:00</updated><id>https://push.cx/voicemail</id><content type="html" xml:base="https://push.cx/voicemail.html"><![CDATA[<p>Robots and elderly relatives have finally driven me to extreme measures.
My voicemail greeting is now this <a href="https://www.youtube.com/watch?v=NI_wyiY4vyk">“you have reached a number that has been disconnected”</a> recording repeated for sixty seconds as a multi-pronged attack on voicemail:</p>

<ul>
  <li>The notes are the “intercept” <a href="https://en.wikipedia.org/wiki/Special_information_tones">special information tones</a> indicating a line is disconnected and the robots should mark the number as dead.</li>
  <li>The words should confuse or bore humans enough to hang up.</li>
  <li>The length should prevent me from getting two-second empty voicemails from people hanging up too slowly.</li>
</ul>

<p>(Unfortunately, my phone company doesn’t let me disable voicemail.)</p>

<p>A potential failure is that I want to continue doing business with a few companies that robocall me with notices (insurance, finance, utilities).
If one of their robots flags my phone number as bad it might become a customer support hassle where they think they no longer have a valid phone number for me.
I’ll edit in an update if this becomes a problem.</p>

<p>2018-06-15: Since this post I’ve only gotten one voicemail (from a relative who read this post). I now only get one spam call every six weeks or so. I wonder if that’s a spammer who tries random numbers without maintaining a database or new spammers entering the market and trying numbers for the first time, but I can’t see a way to effectively differentiate between the two cases and don’t particularly care.</p>]]></content><author><name>{&quot;display_name&quot;=&gt;&quot;Peter Harkins&quot;, &quot;login&quot;=&gt;&quot;admin&quot;, &quot;email&quot;=&gt;&quot;ph@malaprop.org&quot;, &quot;url&quot;=&gt;&quot;http://malaprop.org&quot;}</name><email>ph@malaprop.org</email></author><category term="Life" /><category term="voicemail" /><category term="phones" /><summary type="html"><![CDATA[Robots and elderly relatives have finally driven me to extreme measures. My voicemail greeting is now this “you have reached a number that has been disconnected” recording repeated for sixty seconds as a multi-pronged attack on voicemail:]]></summary></entry><entry><title type="html">Lobsters</title><link href="https://push.cx/lobsters.html" rel="alternate" type="text/html" title="Lobsters" /><published>2017-10-23T10:48:59-05:00</published><updated>2017-10-23T10:48:59-05:00</updated><id>https://push.cx/lobsters</id><content type="html" xml:base="https://push.cx/lobsters.html"><![CDATA[<p><label for="logo " class="margin-toggle">⊕</label>
<input type="checkbox" id="logo " class="margin-toggle" checked="" />
<span class="marginnote"> <a href="http://lobste.rs"><img src="/uploads/2017/10/apple-touch-icon-144.png" alt="Lobsters logo" class="size-full wp-image-2843" width="144" height="144" /></a> </span></p>

<p>I’m now the sysop of <a href="https://lobste.rs">Lobsters</a>, a social news site focused on technology.</p>

<p>Its creator <a href="https://jcs.org">jcs</a> announced three weeks ago that he was ready <a href="https://lobste.rs/s/rk00gm/passing_torch">pass the torch</a>. I’ve been very active on the site, submitting tons of stories to help get the flywheel turning on a solid community. I want to see this good community continue and grow. With the support of the <a href="https://lobste.rs/chat">irc regulars</a>, I stepped up to become the new administrator.</p>

<p>We <a href="https://lobste.rs/s/qduq3a/migration_date_plans">planned the migration</a> and executed it last weekend. There’s a handful of tidying-up tasks on the <a href="https://gist.github.com/pushcx/d81e8002cafadc9e882e51eb49f26377">to-do list</a>, but everything’s running smoothly <a href="https://lobste.rs/s/s7avbx/welcome_new_server">on the new server</a>.</p>

<p>The <a href="https://github.com/lobsters/lobsters">Lobsters codebase</a> has been open source for years and I’m now maintaining it. There have been some great contributions from community members and I’ve <a href="https://github.com/lobsters/lobsters/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22">tagged some issues</a> to encourage new contributors. The new <a href="https://github.com/lobsters/lobsters-ansible">ansible playbook</a> for configuring the site is also public, so hopefully it’ll now be very easy for more people to set up their own sites using the codebase.</p>

<p>I’ve been getting a lot of questions about managing communities in the last week, but I think I’ve already expressed most of my thoughts about it on Lobsters over the years with comments on <a href="https://lobste.rs/s/9pm09z/what_is_on_topic_what_is_not#c_nie0yn">what I think Lobsters is for</a>, the <a href="https://lobste.rs/s/wei3a9/downvote_category_for_inappropriate#c_2ztjbd">environment I want to foster</a>, <a href="https://lobste.rs/s/hstmke/functional_programming_condescension#c_1dfaih">difficulties with brief text on Twitter</a>, and my approach to <a href="https://lobste.rs/s/zsbrjy/why_we_switched_from_python_go#c_lkepiu">effective moderation</a>.</p>

<p>If you’re reading this blog because we’ve met or spoken, please email me for an invitation. Otherwise, check the <a href="https://lobste.rs/u">user list</a> for someone you know or ask <a href="https://lobste.rs/chat">in chat</a> (mention what you want to contribute or link a personal site/github so they know you’re not a random spammer). Hope to see you there!</p>]]></content><author><name>{&quot;display_name&quot;=&gt;&quot;Peter Harkins&quot;, &quot;login&quot;=&gt;&quot;admin&quot;, &quot;email&quot;=&gt;&quot;ph@malaprop.org&quot;, &quot;url&quot;=&gt;&quot;http://malaprop.org&quot;}</name><email>ph@malaprop.org</email></author><category term="Life" /><category term="community" /><category term="lobsters" /><category term="moderation" /><summary type="html"><![CDATA[⊕]]></summary></entry><entry><title type="html">Attending Recurse Center</title><link href="https://push.cx/attending-recurse-center.html" rel="alternate" type="text/html" title="Attending Recurse Center" /><published>2017-08-29T15:22:06-05:00</published><updated>2017-08-29T15:22:06-05:00</updated><id>https://push.cx/attending-recurse-center</id><content type="html" xml:base="https://push.cx/attending-recurse-center.html"><![CDATA[<p>I have been accepted to the <a href="https://www.recurse.com">Recurse Center</a> to spend three months on collaborative, self-directed study of programming. I’m planning to continue studying Haskell and dependent types, proof assistants, and category theory. Maybe Coq, Idris, or TLA+ if I can find someone else interested.</p>

<p>Recurse encourages experienced devs to start or work on open source projects. For a couple years I’ve been taking notes on git’s command-line interface and joking about becoming the first CLI UX specialist, so I think I’m going to start that project as part of my <a href="https://github.com/pushcx/shenzhen">Haskell practice</a>. I don’t plan to re-implement git, I plan to wrap it with more cohesive mental models and useful features for day-to-day development work. If you have thoughts or resources in this area, I’d love to hear about them.</p>

<p>I’ll be attending the Fall 2 session in New York City from September 25 to December 15. If you’re in the NYC area those dates and would like to meet up, reach out, I’d love to meet up. And if you’re in Chicago, well, you won’t see me at <a href="https://push.cx/code-and-coffee">Code and Coffee</a> for a few months.</p>

<p>Edit: I <a href="https://push.cx/2017/return-statement">returned</a>.</p>]]></content><author><name>{&quot;display_name&quot;=&gt;&quot;Peter Harkins&quot;, &quot;login&quot;=&gt;&quot;admin&quot;, &quot;email&quot;=&gt;&quot;ph@malaprop.org&quot;, &quot;url&quot;=&gt;&quot;http://malaprop.org&quot;}</name><email>ph@malaprop.org</email></author><category term="Life" /><category term="New York City" /><category term="Recurse Center" /><summary type="html"><![CDATA[I have been accepted to the Recurse Center to spend three months on collaborative, self-directed study of programming. I’m planning to continue studying Haskell and dependent types, proof assistants, and category theory. Maybe Coq, Idris, or TLA+ if I can find someone else interested.]]></summary></entry><entry><title type="html">MiscPodcast</title><link href="https://push.cx/miscpodcast.html" rel="alternate" type="text/html" title="MiscPodcast" /><published>2017-08-27T16:02:07-05:00</published><updated>2017-08-27T16:02:07-05:00</updated><id>https://push.cx/miscpodcast</id><content type="html" xml:base="https://push.cx/miscpodcast.html"><![CDATA[<p>I have some random episodes of podcasts laying around waiting to get listened to from podcasts I don’t (yet) care to subscribe to. Maybe they had an interesting guest or topic, or came recommended. These downloads will lay around on my computer for months because they’re not in my podcasting app, so they’re not really in my listening queue.</p>

<p>So I hacked up a PHP script to grab those mp3s and serve a valid (if spartan) podcast feed.</p>

<p>You can find the code <a href="https://github.com/pushcx/miscpodcast">on GitHub</a>. It’s designed to be dropped into place rather than robustly deployed. Then I download mp3s to that folder and they’re delivered as my own personally-curated podcast feed.</p>

<p>Thanks to Shubham Jain for his <a href="https://github.com/shubhamjain/PHP-ID3">PHP-ID3</a> library so the script can read some metadata into the feed.</p>]]></content><author><name>{&quot;display_name&quot;=&gt;&quot;Peter Harkins&quot;, &quot;login&quot;=&gt;&quot;admin&quot;, &quot;email&quot;=&gt;&quot;ph@malaprop.org&quot;, &quot;url&quot;=&gt;&quot;http://malaprop.org&quot;}</name><email>ph@malaprop.org</email></author><category term="Code" /><category term="PHP" /><category term="podcast" /><category term="project" /><summary type="html"><![CDATA[I have some random episodes of podcasts laying around waiting to get listened to from podcasts I don’t (yet) care to subscribe to. Maybe they had an interesting guest or topic, or came recommended. These downloads will lay around on my computer for months because they’re not in my podcasting app, so they’re not really in my listening queue.]]></summary></entry><entry><title type="html">Arithmetic Wrap in GMS2</title><link href="https://push.cx/arithmetic-wrap-in-gms2.html" rel="alternate" type="text/html" title="Arithmetic Wrap in GMS2" /><published>2017-08-06T21:50:29-05:00</published><updated>2017-08-06T21:50:29-05:00</updated><id>https://push.cx/arithmetic-wrap-in-gms2</id><content type="html" xml:base="https://push.cx/arithmetic-wrap-in-gms2.html"><![CDATA[<p>I’m learning GameMaker Studio 2 because my 10-year old nephew wants to make video games (and the 10 year old inside of me wants to make video games, too). It’s a nice toolkit and IDE for games, <a href="https://twitter.com/maxkreminski/status/887815522061926400">very beginner-friendly</a>, with a friendly community. It’s even been used in some <a href="https://www.yoyogames.com/showcase">highly polished and popular games</a>. If you’re curious, there’s a ~90 minute <a href="https://www.youtube.com/playlist?list=PLhIbBGhnxj5IcGWhJQNF5hScmCCn4M3xg">tutorial playlist</a> that’s easy to skim as a demo.</p>

<p>Pricing is reasonable, it’s $100 to export desktop games, couple hundred more to export to iOS + Android, HTML 5, even PS4 and Xbox. And this week version 1.4 (with a free trial and discounted upgrade to version 2) is <a href="https://www.humblebundle.com/gamemaker-rebundle">on sale cheap</a>.</p>

<p>The games are coded in “GameMaker Language”, which is approximately PHP 3. Little more OO, little less coercion, but it feels similarly very focused on its niche without much experience behind it. I’ve been mostly coding Haskell the last few weeks, so there’s a bit of whiplash moving between the two.</p>

<p>There’s some great video tutorials on YouTube showing how to make games like <a href="https://www.youtube.com/playlist?list=PLsLwv4RXTczJyMxB8znlQkW7H3kEYfYQ6">NES Zelda</a>, <a href="https://www.youtube.com/playlist?list=PLSFMekK0JFgzbFfj1vAsyluKTymnBiriY">Farming RPG</a>, and <a href="https://www.youtube.com/playlist?list=PLPRT_JORnIupqWsjRpJZjG07N01Wsw_GJ">Platformer</a>. All three of those channels are worth clicking around on as they have other good playlists or one-off videos.</p>

<p>On that last channel I saw a video on <a href="https://www.youtube.com/watch?v=2FroAhEsuE8">Useful Scripts for GMS2</a> presenting five code snippets. In GML, a “script” is roughly a singleton static method object used to centralize game state or encapsulate snippets of functionality.</p>

<p>The third script presented <a href="https://youtu.be/2FroAhEsuE8?t=5m18s">at 5:18 in the video</a> was <code class="language-plaintext highlighter-rouge">wrap(value, min, max)</code> which wraps values that exceed the min or max back around to the other side. So <code class="language-plaintext highlighter-rouge">wrap(5, 0, 9)</code> is <code class="language-plaintext highlighter-rouge">5</code> because it’s in the bounds, but <code class="language-plaintext highlighter-rouge">wrap(11, 5, 9)</code> is <code class="language-plaintext highlighter-rouge">6</code> because it wraps 2 past 9. There’s a visual in the video at 5:30 that makes it real clear. There’s also a screenshot of the code:</p>

<p>/// @description Wrap(value, min, max)
/// @function Wrap
/// @param value
/// @param min
/// @param max
// Returns the value wrapped, values over or under will be wrapped around</p>

<p>if (argument0 mod 1 == 0)
{
while (argument0 &gt; argument2 || argument0 &lt; argument1)
{
if (argument0 &gt; argument2)
argument0 += argument1 - argument2 - 1;
else if (argument0 &lt; argument1)
argument0 += argument2 - argument1 + 1;
}
return(argument0);
}
else
{
var vOld = argument0 + 1;
while (argument0 != vOld)
{
vOld = argument0;
if (argument0 &lt; argument1)
argument0 = argument2 - (argument1 - argument0);
else if (argument0 &gt; argument2)
argument0 = argument1 + (argument0 - argument2);
}
return(argument0);
}</p>

<p>There’s a lot going on there. This is probably hot-path code that runs every frame, but it has branches inside a loop for something that could almost certainly be an arithmetic one-liner. So for practice with GML and the IDE in general, I rewrote it.</p>

<p>I saved the above script as <code class="language-plaintext highlighter-rouge">original_wrap</code> and created my own <code class="language-plaintext highlighter-rouge">wrap</code> implementation:</p>

<p>/// @description wrap(value, min, max)
/// @function wrap
/// @param value The value to wrap into the bounds
/// @param min Minimum bound, inclusive
/// @param max Maximum bound, inclusive
// Returns the value wrapped to the range [min, max] (min and max can be swapped).
// Calls floor() on reals, but GML’s modulo is doing something weird and original_wrap just hangs indefinitely on some values anyways so oh well.</p>

<p>var value = floor(argument0);
var _min = floor(min(argument1, argument2));
var _max = floor(max(argument1, argument2));
var range = _max - _min + 1; // + 1 is because max bound is inclusive</p>

<p>return (((value - _min) % range) + range) % range + _min;</p>

<p>Some oddities, like JavaDoc instead of a function signature, so arguments have automatic names (reminds me of perl 5). In dev I got a compiler error for referencing <code class="language-plaintext highlighter-rouge">argument1</code> before <code class="language-plaintext highlighter-rouge">argument0</code> - I’m not sure what that could be but look forward to reading <a href="http://docs2.yoyogames.com/">the manual</a>. I can’t unimport/shadow the global <code class="language-plaintext highlighter-rouge">max</code> and <code class="language-plaintext highlighter-rouge">min</code>, the convention seems to be to use a leading underscore for colliding names.</p>

<p>To test it, I created another script called <code class="language-plaintext highlighter-rouge">test</code> and invoked it from an object’s create event. Which felt a little roundabout for specifying that I wanted it to run at startup, but I’ve barely touched the manual so I’m probably missing something obvious. I know there’s 3rd-party <a href="https://github.com/gm-core/gamatas">test library</a> but I wanted to hand-roll to see more moving pieces. I generated a <a href="https://craftedsw.blogspot.com/2012/11/testing-legacy-code-with-golden-master.html">golden master</a> test suite to exercise a bunch of test data, though I didn’t go all the way into property-based testing:</p>

<p>// make this easy to spot in the build output
show_debug_message(“HELLO &amp;&amp;&amp;&amp;&amp;&amp;&amp;&amp;&amp;&amp;&amp;&amp;&amp;&amp;&amp;&amp;&amp;&amp;&amp;&amp;&amp;&amp;&amp;&amp;&amp;&amp;&amp;&amp;&amp;&amp;&amp;&amp;&amp;&amp;&amp;&amp;&amp;&amp;&amp;&amp;&amp;&amp;&amp;&amp;&amp;&amp;&amp;&amp;&amp;&amp;&amp;&amp;&amp;&amp;&amp;&amp;&amp;&amp;&amp;&amp;&amp;&amp;&amp;&amp;&amp;&amp;&amp;&amp;&amp;&amp;&amp;&amp;”);</p>

<p>for(i = -5; i &lt; 25; i++) {
var orig = original_wrap(i, -2, 5);
var new = wrap(i, -2, 5)
if (orig != new) {
show_debug_message(“int failed “ + string(i) + “ orig “ + string(orig) + “ new “ + string(new));
}
}</p>

<p>//for(i = -5.0; i &lt; 19.2; i = i + 1.1) {
// show_debug_message(i);
// var orig = original_wrap(i, -2.1, 5.3);
// var new = wrap(i, -2.1, 5.3)
// if (orig != new) {
// show_debug_message(“real failed “ + string(i) + “ orig “ + string(orig) + “ new “ + string(new));
// }
//}</p>

<p>The commented-out testcase is, I think, about the broken behavior with regards to reals. I’m pretty sure the <code class="language-plaintext highlighter-rouge">(argument0 mod 1 == 0)</code> exists to type-check if <code class="language-plaintext highlighter-rouge">argument0</code> is an integer or real. I’m not sure because GML has <a href="http://docs2.yoyogames.com/source/_build/3_scripting/3_gml_overview/checking_data_types/typeof.html"><code class="language-plaintext highlighter-rouge">typeof</code></a> for typechecks (<a href="http://docs.yoyogames.com/source/dadiospice/002_reference/001_gml%20language%20overview/data%20types.html"><code class="language-plaintext highlighter-rouge">is_real</code></a> in 1.4, which feels very PHP 3), but this is probably well-copy-pasted newbie code. When I tried to test the behavior, I ran into a bug where <code class="language-plaintext highlighter-rouge">original_wrap</code> hung indefinitely on some inputs (the existing test triggers this, if you want to uncomment and run). I didn’t want to keep tinkering, so I dropped in some <code class="language-plaintext highlighter-rouge">floor</code> calls and moved on.</p>

<p>Anyways, this was some fun tinkering. I’m looking forward to working through the official tutorial with my nephew and maybe making some small games. (Oh, and I release this into the public domain, feel free to use it with or without credit.)</p>]]></content><author><name>{&quot;display_name&quot;=&gt;&quot;Peter Harkins&quot;, &quot;login&quot;=&gt;&quot;admin&quot;, &quot;email&quot;=&gt;&quot;ph@malaprop.org&quot;, &quot;url&quot;=&gt;&quot;http://malaprop.org&quot;}</name><email>ph@malaprop.org</email></author><category term="Code" /><category term="gamedev" /><category term="gms2" /><summary type="html"><![CDATA[I’m learning GameMaker Studio 2 because my 10-year old nephew wants to make video games (and the 10 year old inside of me wants to make video games, too). It’s a nice toolkit and IDE for games, very beginner-friendly, with a friendly community. It’s even been used in some highly polished and popular games. If you’re curious, there’s a ~90 minute tutorial playlist that’s easy to skim as a demo.]]></summary></entry><entry><title type="html">Redshift With Cloudiness Adjustment</title><link href="https://push.cx/redshift-with-cloudiness-adjustment.html" rel="alternate" type="text/html" title="Redshift With Cloudiness Adjustment" /><published>2017-01-25T12:32:37-06:00</published><updated>2017-01-25T12:32:37-06:00</updated><id>https://push.cx/redshift-with-cloudiness-adjustment</id><content type="html" xml:base="https://push.cx/redshift-with-cloudiness-adjustment.html"><![CDATA[<p>A <a href="https://lobste.rs/s/y7cv5r/blue_light_has_dark_side_harvard_health">Lobsters story</a> on the bright blue light of displays reminded me I should post this. I use <a href="http://jonls.dk/redshift/">redshift</a> to adjust the color temperature of my monitor at night so I sleep better, and I wrote a custom wrapper script to include an adjustment for how overcast it is.</p>

<p>#!/bin/bash</p>

<p>set -e # exit on error
#set -x # debugging</p>

<p>if [ `pidof /usr/bin/redshift` ] ; then exit ; fi</p>

<p># Chicago
LAT=41.94
LONG=-87.65</p>

<p>MAX=5700
DOWNTO=4200
# Tungsten: 2700K
# Halogen: 3400K
# Fluorescent: 4200K
# Daylight: 5500K
MODE=vidmode
GAMMA=0.8</p>

<p># figure out how overcast it is, and adjust temperature to match
TODAY=`date +%Y-%m-%d`
wget -q -O/tmp/clouds.xml “http://graphical.weather.gov/xml/sample_products/browser_interface/ndfdXMLclient.php?product=time-series&amp;sky=sky&amp;lat=$LAT&amp;lon=$LONG&amp;begin=${TODAY}T00:00:00&amp;end=${TODAY}T23:59:59”
OVERCAST=`echo -e ‘s/[\^0-9]*([0-9]+)&lt;\/value&gt;/\1/m\nt xxx\nd\n:xxx’ |sed -rf - /tmp/clouds.xml |tail -n 1`
TEMP=`echo “$MAX+${OVERCAST}0” |bc`</p>

<p>redshift-gtk -l $LAT:$LONG -t $TEMP:$DOWNTO -g $GAMMA -m $MODE</p>]]></content><author><name>{&quot;display_name&quot;=&gt;&quot;Peter Harkins&quot;, &quot;login&quot;=&gt;&quot;admin&quot;, &quot;email&quot;=&gt;&quot;ph@malaprop.org&quot;, &quot;url&quot;=&gt;&quot;http://malaprop.org&quot;}</name><email>ph@malaprop.org</email></author><category term="Code" /><category term="bash" /><category term="redshift" /><summary type="html"><![CDATA[A Lobsters story on the bright blue light of displays reminded me I should post this. I use redshift to adjust the color temperature of my monitor at night so I sleep better, and I wrote a custom wrapper script to include an adjustment for how overcast it is.]]></summary></entry></feed>