{"version":"https://jsonfeed.org/version/1.1","title":"Blog.WorldMaker.net","home_page_url":"https://blog.worldmaker.net/","feed_url":"https://blog.worldmaker.net/feed.json","description":"Max Battcher's personal blog","language":"en","authors":[{"name":"Max Battcher","url":"https://worldmaker.net"}],"items":[{"id":"https://blog.worldmaker.net/2026/05/09/quick-updates/","url":"https://blog.worldmaker.net/2026/05/09/quick-updates/","title":"Quick Updates","content_html":"<p>I was skimming old blog articles and realized it\nhas been a while since I've done small posts. Some\nquick updates:</p>\n<ul>\n<li>I separated from my job in April, so I have my\nshingle out and am now looking for new work.\nMaybe <a href=\"https://blog.worldmaker.net/2026/04/29/simple-oxymoron/\">given my feelings on recent &quot;AI&quot; trends</a>\nI'm looking for a new career.</li>\n<li>I procrastinated my job search by spending a\nweek cleaning my house and then hosting my first\n<a href=\"https://blog.worldmaker.net/tag/thunder/\">Thunder</a> party in too long.</li>\n<li>I procrastinated my job search by having a lot\nof fun building new features for <a href=\"https://blog.worldmaker.net/2025/04/27/book-club/\">my book club's site</a>.\nMaybe I'm not entirely done with my career, I\njust need someone to trust my creativity. Also,\nI had to migrate the site to a different backend.\nTech debt touches everything.</li>\n<li>Speaking of tech debt, I procrastinated my job\nsearch yesterday by migrating my entire blog\nfrom Jekyll to <a href=\"https://lume.land/\">Lume</a>. I was tired of Ruby\ngem Dependabot warnings that never felt like\nthey mattered to me, and I was tired of a\npreview experience that required a lot of WSL\nshenanigans. I've become a big fan of Lume as\na very powerful static site generator. (I'm\ndoing some pretty complex fun things in that\nBook Club site.) It's nice to have good Tags\npages again.</li>\n<li><a href=\"https://portless.sh/\">Portless</a> has some Windows quirks but is\nhandy enough to forgive them.</li>\n<li>News of my separation from my job reached me\nin the middle of my vacation. It feels weird\nthat has <a href=\"https://blog.worldmaker.net/2015/03/03/recent-activity/\">happened twice</a> now. I know it is\njust business, but it felt deeply personal,\nespecially given an end date that coincided with\nmy birthday weekend. I spent the first half of\nmy vacation having an incredible time at\nDisneyland. Disneyland kept asking me questions\nabout if I have enough creativity in my life.\nI kept asking questions why Disney doesn't have\nenough creativity in its apps and if I could\nfind a way to explore that professionally. I\nthink I was going to be looking for new\nchallenges this year even if my job hadn't\nforced me to consider it the hard way between\nan incredible week at Disneyland and a lovely\nweek on JoCoCruise 2026, both with a lot of\ngreat friends doing awesome things.</li>\n</ul>\n","date_published":"2026-05-09T00:00:00.000Z"},{"id":"https://blog.worldmaker.net/2026/04/29/simple-oxymoron/","url":"https://blog.worldmaker.net/2026/04/29/simple-oxymoron/","title":"The Simple Oxymoron of \"AI-Driven Innovation\" is Actually Risk Adverse","content_html":"<p>&quot;AI-Driven Innovation&quot; is an oxymoron to excuse over-relying on &quot;legacy\ncode as a service&quot;. I don't think there is any sort of paradox here in\nthe way that &quot;AI-Driven Innovation&quot; as a strategy is in consequence risk\nadverse and not a strategy for innovation and creativity. I don't see a\nneed to bury any ledes here and I think this is awful on multiple levels.\nI worry it is going to take too many years for companies to realize the\nmistakes that they are making right now. It's had me making too many\njokes to hide the depression that I feel that I need to find a new\ncareer altogether.</p>\n<p>&quot;AI-Driven Innovation&quot; is an investment in a rental economy of &quot;legacy\ncode as a service&quot;. Legacy code is always an indicator of some of the\nmost risk adverse pieces of a company's software portfolio.</p>\n<h1>Legacy Code as a Service</h1>\n<p>I have spent so much of my career in &quot;the legacy code mines&quot;. It's\nalways been a skill set I've tried to downplay on my resume. It's always\nbeen a skill set I've kept being asked to return to because I've earned\na lot of experience in it, often the hard way. For decades, I've joked\nthat my &quot;retirement&quot; plan was to find a cushy high paying, low effort\nCOBOL job.</p>\n<p>But now I don't think those joked about jobs are going to be so easy to\nfind, not because the LLMs are going to replace all that COBOL code but\nthat the LLMs are going to help companies build so much more, equivalent\nor worse legacy code faster than ever. The LLMs are (unfortunately for\nmy retirement fancies) commoditizing legacy code. They are making it\nplentiful. They are giving tomorrow's legacy code experts more work than\never before at an unprecedented scale.</p>\n<p>I've been accused about being overly unfair to LLMs by calling their\noutput &quot;legacy code as a service&quot;, but think about it: which companies\noffered their truly innovative, proprietary, most modern, most\ndomain-driven codebases to LLMs for training? LLMs were trained on open\nsource and a lot of open source is just a funny place to stash &quot;someone\nelse's legacy code&quot; or at least in the most generous cases &quot;some other\nbusiness' complement&quot;.</p>\n<p>If AI can't train on the innovative software, how can it help you find\ninnovation in your own software strategies?</p>\n<h1>There is No Creative Ghost in the Machine</h1>\n<p>Show me the part of the LLM family of algorithms that is the &quot;creativity&quot;\nand I can show you a slot machine and try to teach you about the Gambler's\nFallacy.</p>\n<p>People are inherently bad at reasoning about probability-based algorithms\n(and probability and statistics math in general) and some of the current\nlove affair of LLMs seems explicitly to be that people don't understand\nthe randomness of algorithms deeply associated with probability and are\nhappy to anthropomorphize it or assign to it a creativity or &quot;surprise&quot;\nthat is really just random variation on a theme (and eventual regression\nto the mean).</p>\n<p>You could put a lot of creativity into the input of the system, so called\n&quot;prompt engineering&quot;, but what good is laundering that much creativity\nthrough a machine designed to produce the most statistically average\noutputs from it?</p>\n<p>That of course entirely relies on people seeing &quot;prompt engineering&quot; as\na creative tool for innovation. Most of the companies seeking &quot;AI\nInnovation&quot; wrote off software as &quot;overhead&quot; and &quot;cost centers&quot; decades\nago. In a lot of those companies the creativity of the software engineer\nis a defect in what should be cogs in a well-oiled machine that takes\nbusiness requirements and spits out software. &quot;AI-Driven Innovation&quot;\nonly continues to further misplace and confuse the sources of creativity\nin software development. These companies aren't going to learn anything\nfrom the mistakes they are about to make. (The mistakes they are already\nmaking.)</p>\n<h1>There is No Warranty Among Thieves</h1>\n<p>When you hire a software engineer, they have an ownership and a\nresponsibility to the code they write. When working on legacy code, that\nperson with any sort of ownership is already gone, and that is almost\nalways an impediment. Some of the key skills on working on a legacy code\ncodebase are essentially mind-reading, trying to read everything <em>that</em>\nperson wrote around the time they wrote that software that you can get\nsome semblance of feeling like you can at least temporarily think like\nthey used to think in those days on that project.</p>\n<p>LLMs are incredible in how much they are a (soylent) sausage factory for\ntaking all those human pieces (the poetry hidden in the required poetry\nforms) and smearing them into a reconstituted meat slurry. The output\ncode has no ownership and is divorced from any ownership it might have\nonce had in a gelatinous puree. There's no way to read everything &quot;<em>that</em>\ndeveloper&quot; wrote at the time an LLM output what it did. We don't have\naccess to the full training sets. Even if we did, the major models\nthemselves fully retrain at a roughly six month cadence and aren't the\nsame from &quot;version to version&quot;. Sometimes they aren't the same from\nmonth to month due to partial retraining that the model makers don't\nfeel is worth a version bump. (An LLM is a Ship of Theseus by design.)</p>\n<p>Even if you could somehow connect the code that the LLM has produced to\nspecific open source code via the training set that you don't have access\nto, one of the first safety gates in almost every open source license is\na &quot;No Warranty&quot; clause. It's one of the foundations that keeps open\nsource a viable practice. (Just as software developers generally don't\nkeep ownership when they are severed from a company is a sort of a\nfoundational creator of legacy code.) Many open source projects will\noffer services similar to a &quot;warranty&quot; or &quot;support contract&quot; for some\nmaterial cost (sometimes even just as volunteer effort returned the open\nsource community). Yet the LLM can't provide even this disclosure, can't\nsell you that service, can't tell you what exactly you have &quot;licensed&quot;\nor how many &quot;No Warranty&quot; clauses are between you and whoever originally\nowned the code it just output. There's not just &quot;no owner&quot; there is a\nmulti-layer onion of &quot;negative ownership&quot;. Your EULA with the LLM\ndeclares &quot;No Warranty&quot;. Most of the code the LLM trained on declares\n&quot;No Warranty&quot;. Who really owns anything? It's all just a rental to some\nlandlord, as a <em>service</em>.</p>\n<p>Then there is the added awful layer in that onion that much of the code\nthe LLM trained on was also effectively stolen, according to the licenses\nand agreements in place around that code. A lot of the open source world\nis built on &quot;copyleft&quot; licenses designed to legally encourage &quot;what you\ntake from the open source community you volunteer to bring back and\ncontinue to enrich the community with your own contributions&quot;. LLMs\ntoday are ignoring these legal requirements. LLMs are not attempting to\nenrich the community with their sausage factories, they are trying to\nclaim that they don't need to because the sausage is so different from\nthe animals it was processed out of. In the process of reconstituting\nthat meat slurry they claim that they've liberated it from such concerns\nas &quot;this is an illegal slaughterhouse&quot; and &quot;this is a health code\nviolation&quot; and &quot;this is just generally a violation of common sense human\ndecency&quot;.</p>\n<p>Is that a great foundation for &quot;AI-Driven Innovation&quot;? Does a\nnasty-smelling onion of &quot;no one owns this code&quot;, &quot;no one wants to know\nwhat the real ingredients in this code are&quot;, and &quot;if they did know all\nthe ingredients in this it would be illegal&quot; sound like &quot;innovation&quot;?\nThere was an era in American economics when companies innovated on how\nmuch diseased rat meat they could get away with in their sausage. I\ncan't be alone in thinking this will all end poorly. I'm also sure I'm\nnot alone in thinking this won't end poorly fast enough to save us from\nso many of the worst mistakes.</p>\n<h1>Garbage In, Garbage Out</h1>\n<p>&quot;AI-Driven Innovation&quot; is a garbage plan of creating garbage inputs to\nsoftware that produce nothing but statistically probable legacy code, as\na service. It's a simple oxymoron. There's no real &quot;innovation&quot; to be\nfound in the output. It's going to keep checking boxes in PowerPoint\nslides to an investor class that has bought into &quot;AI-Driven Innovation&quot;\nas a shared delusion of their (inherent disconnect from) reality rather\nthan accepting it to be a sad improbable dream that makes no sense when\neasily questioned. In the meantime a lot of software is going to regress\nto some ugly averages and real software innovation will take a huge hit.</p>\n<p>In the mean time, the truly innovative, experienced software engineers\nare encouraged to accept layoffs if they can't &quot;align&quot; on &quot;AI-Driven\nInnovation&quot;. (Leaving plenty of now immediately legacy code behind in so\ndoing.) In the mean time, tomorrow's truly innovative software engineers\nare told to avoid growing real experience and instead feed LLMs to\ngenerate so much mediocrity, if they have jobs at all. In the mean time,\n&quot;AI-Driven Innovation&quot; is crafting a mesmerizing illusion that software\nas a labor practice is over and companies don't really need software\ndevelopers at all.</p>\n<p>Garbage in, garbage out, in a feedback loop threatening to spiral to\ndestroy real innovation in software development.</p>\n","date_published":"2026-04-29T00:00:00.000Z"},{"id":"https://blog.worldmaker.net/2026/04/07/redux-net/","url":"https://blog.worldmaker.net/2026/04/07/redux-net/","title":"A Pattern for Redux with Epics in C# Dependency Injection","content_html":"<p>This is a day job project that for a bit I thought might spin out into\nan open source library but the funny thing about it is that it has been\nmore of a chisel the statue out of the marble type of project. It\nstarted from a complicated place and the further we've pushed it towards\nRedux best practices the more it disappears into &quot;just&quot; being primarily\na pattern of C#'s out-of-the-box dependency injection.</p>\n<p>(For some small context, the original design that this design migrated\naway from was far more inspired as something of a telephone game to\nAngular's libraries such as NgRx, which themselves were a telephone\ngame away from Redux.)</p>\n<p>It still seems worth publicly documenting for the next team looking for\nthis pattern. I wish there was a library I could point to to install\nthis pattern into your project, hopefully this documentation will\nsuffice as a good starting point. To some extent, the lack of a need for\nan external library is a small triumph and a proof that this can be a\ngood way to build your C# &quot;view state&quot; model, for projects that want a\nReactiveX-&quot;native&quot; alternative to state management and/or event bus\npatterns such as MVC, MVVM, Mediatr.</p>\n<h1><code>RootAction</code></h1>\n<p>The core type for this Redux design is <code>RootAction</code>. This is not a\nparticularly great name, but it does avoid conflict with\n<code>System.Action&lt;T&gt;</code> (the <code>void</code>-relative of <code>System.Func&lt;T, U&gt;</code>). In\nproper Redux fashion, this is a very simple type with only a required\nfield name <code>Type</code>.</p>\n<pre><code class=\"language-cs\">public record RootAction\n{\n    public virtual string Type =&gt; $&quot;{GetType().Name}&quot;;\n}\n</code></pre>\n<p>There are a bunch of strong notes to be made here, some of which remained\ndebates for some time after moving to this pattern. The NgRx family never\nfully understood the importance of Actions as a serializable type, but\nit is a useful core principle to many Redux pattern benefits. Serializable\ntypes can be output to logs for replay, for instance.</p>\n<p>Related to replay, that is something the day job project never got around\nto implementing. At one point Source Generators were explored to replace\nthe reflection-based <code>Type</code> seen here with something more useful to\n<a href=\"https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json/polymorphism\"><code>System.Text.Json</code>'s polymorphic</a> support.</p>\n<p>The advantage to this form of base <code>Type</code> implementation was that a tree\nof types was relatively easy to build. Example:</p>\n<pre><code class=\"language-cs\">public record CoolFeatureAction : RootAction\n{\n    public override string Type =&gt; $&quot;CoolFeature/{GetType().Name}&quot;\n}\n\npublic sealed record CoolFeatureInitialized(bool IsCool) : CoolFeatureAction;\n</code></pre>\n<p>One of the things that this also highlights is that the Redux world\nbest practice of writing &quot;action creators&quot; is handled automatically by\nC# record syntax. The primary record constructor is already laid out as\na Redux-like &quot;action creator&quot;, and provides similar refactoring benefits.</p>\n<p>A few of the other tips about Actions from the Redux pattern best\npractices that may be useful to keep in mind at this point: Actions are\ngenerally named like Events, they are named as the &quot;user interaction that\njust happened&quot; in the past tense. Actions should generally not be &quot;mere\nsetters&quot; (<code>FieldASet</code>, <code>FieldBSet</code>) but rather State <em>transactions</em> as\na high-level event (<code>EntireFeatureInitialized</code> with everything needed\nto update <code>FieldA</code> and <code>FieldB</code> all at once).</p>\n<h2>Option: Popping the Middle Layer of Actions</h2>\n<p>Another brief digression from JSON polymorphic debates was if the\n<code>RootAction</code>'s <code>Type</code> property implementation should have been, assuming\nReflection was still fine:</p>\n<pre><code class=\"language-cs\">public record RootAction\n{\n    public string Type =&gt; $&quot;{GetType().FullName.Replace('.', '/')}&quot;;\n}\n</code></pre>\n<p>In which case the inheritance tree might have been a little flatter in\nexchange for using the namespace hierarchy automatically. (With the idea\nof the <code>Replace</code> being to still prefer the Redux dev tools suggested <code>/</code>\nnamespace separator instead of .NET's <code>.</code>.)</p>\n<h2>Performance Digression on Reference Types</h2>\n<p>In part from references libraries that heavily relied on .NET's value\ntypes (typically, but not just, <code>readonly record struct</code>) to deeply\navoid nulls (at the risk of sometimes equally confusing &quot;zeroed&quot;\n<code>default(T)</code> from structs) the application had picked up the premature\noptimization of using value types for a lot of internal state data.</p>\n<p>This was a performance problem that we caught early in the Redux\npattern refactor. It is easy to forget that value types are generally\npassed by value which means <em>by copy</em> in many cases. Especially in a Redux\ndesign you often can't afford all the memory of every State and every\nAction to be copied for every Reducer (and Epic and view component, etc).</p>\n<p>Don't forget that <code>public record</code> is shorter than\n<code>public readonly record struct</code> for a good reason and is a good default.\nReference types aren't an enemy to performance in .NET, they are just as\noften the solution. Don't forget to apply performance profiling tools\nfor real data from your applications and not just default to value types\nbecause they sometimes help performance or null safety.</p>\n<h1>Reducers</h1>\n<p>The heart of the Redux pattern is Reducers to apply Actions to State. In\nC# we used a very simple <code>IReducer</code> definition from which a number of\nthings inherited, include a <code>Reducer&lt;Action, State&gt;</code> base type for single\nAction reducers.</p>\n<pre><code class=\"language-cs\">public interface IReducer&lt;State&gt;\n{\n    public State Handle(RootAction action, State state);\n}\n\npublic abstract class Reducer&lt;Action, State&gt;\n    : IReducer&lt;State&gt; where Action : RootAction\n{\n    public abstract State Reduce(Action action, State state);\n\n    public State Handle(RootAction rootAction, State state) =&gt; rootAction switch\n    {\n        Action action =&gt; Reduce(action, state),\n        _ =&gt; state,\n    };\n}\n</code></pre>\n<p>The base class is a pattern in C# you also see in things like Authorization\nPolicy Handlers, a type-safe &quot;trampoline&quot; from the generic <code>RootAction</code>\nto a specific <code>Action</code> (we had less of a problem with a type parameter\nsometimes shadowing <code>System.Action&lt;T&gt;</code> than a top-level type doing so).\n(This is also Typescript influence here of dropping the <code>T</code>-prefix\nsemi-Hungarian notation for type parameters; though as with most C#\nnaming schemes the <code>I</code>-prefix for interfaces is maybe eternal at this\npoint.)</p>\n<p>Because the trampoline is often the shortest way to write many Reducers,\nthe <code>IReducer&lt;State&gt;</code> method borrows the name <code>Handle</code> from places like\nPolicy Handlers so that <code>Reducer&lt;Action, State&gt;</code> can use the more domain\nfriendly <code>Reduce</code> as its more specific abstract function.</p>\n<p>It's also important to note that <code>IReducer&lt;State&gt;</code> is intentionally and\n<em>only</em> synchronous. That's an ancient Redux best practice. That will also\nbe something we return to as we get to Epics.</p>\n<p>To round out our <code>CoolFeature</code> example we might have an example Reducer\nlike so:</p>\n<pre><code class=\"language-cs\">public record CoolThing(string Name, /* … */ DateTimeOffset Created);\n\npublic record CoolFeatureState(\n    bool IsCool,\n    DateTimeOffset CoolStarted,\n    IEnumerable&lt;CoolThing&gt; CoolThings)\n{\n    public static readonly CoolFeatureState Disabled = new(false, DateTimeOffset.MinValue, []);\n}\n\npublic class CoolFeatureInitializedReducer(TimeProvider timeProvider)\n    : Reducer&lt;CoolFeatureInitialized, CoolFeatureState&gt;\n{\n    public override CoolFeatureState Reduce(\n        CoolFeatureInitialized action,\n        CoolFeatureState state) =&gt; state with\n    {\n        IsCool = action.IsCool,\n        CoolStarted = timeProvider.GetLocalNow(),\n        // ASIDE: Injecting a TimeProvider is a great way to increase\n        // unit testing powers of this reducer because you can test in\n        // fake time with a FakeTimeProvider.\n    };\n}\n\npublic static class CoolFeatureExtensions\n{\n    public static IServiceCollection AddCoolFeature(this IServiceCollection services) =&gt; services\n        .AddTransient&lt;IReducer&lt;CoolFeatureState&gt;, CoolFeatureInitializedReducer&gt;();\n}\n</code></pre>\n<p>Note that while Reducers are required to be synchronous, they are still\nfree to inject and use other dependencies (so long as they are also\nsynchronous). Similarly, reducers may be state machines that use complex\ncalculations based on the current state to produce the next state. The\nbig restriction is <em>synchronous</em>.</p>\n<h1>Important Digression into <code>Lens&lt;A, B&gt;</code></h1>\n<p>Before we can talk about the Store and Slices, we need one more tool in\nour toolbox.</p>\n<p>In Typescript, ala the original Redux pattern, we can easily &quot;slice&quot; a\nState via a string key of that state. Typescript is also friendly in that\nthis can be relatively type safe thanks to meta-typing tools like\n<code>T[keyof T]</code>.</p>\n<p>Doing that type-safely in C# is a bit more work. One of the tools we can\nuse to do it that is useful as a generic type for immutable data structures\ncan be borrowed from functional programming and is called a Lens. A Lens\nis really just a type-safe <em>getter</em> from one item to another (given an\n<code>A</code>, I want a <code>B</code>), and a type-safe <em>setter</em> in the other direction\n(given a <code>B</code>, I would like an updated <code>A</code>).</p>\n<p>There are several functional programming libraries that provide a\n<code>Lens&lt;A, B&gt;</code> useful in C#, including <a href=\"https://github.com/louthy/language-ext\">LanguageExt</a>, but <code>Lens&lt;A, B&gt;</code>\nreally is just as simple as described, so we can also just implement our\nown quite simply:</p>\n<pre><code class=\"language-cs\">public record Lens&lt;A, B&gt;(\n    Func&lt;A, B&gt; Get,\n    Func&lt;B, Func&lt;A, A&gt;&gt; SetF\n)\n{\n    public A Set(B value, A container) =&gt; SetF(value)(container);\n}\n</code></pre>\n<p>There's a few other convenience functions you could implement (often\nnamed <code>Update</code>), but that's the important heart of <code>Lens&lt;A, B&gt;</code>.</p>\n<p>The importance of <code>SetF</code> in this form as <code>Func&lt;B, Func&lt;A, A&gt;&gt;</code> as the\nunderlying <em>setter</em> is maybe not obvious if you haven't spent enough time\nin languages that support currying such as F# and Haskell, but is one of\nthe useful properties for making <code>Lens&lt;A, B&gt;</code> generically useful to\nseveral of our needs.</p>\n<p>One useful way to see it from Redux points of view is that\n<code>SetF(someFixedValue)</code> creates just about the most simple type of Reducer\npossible (given a State, yield the next State). It's a handy abstraction.</p>\n<h1>Handy Digression into <code>ShareReplay</code></h1>\n<p>We are taking a ReactiveX native approach to our Store that follows to\nencourage consistent use of Epics (more on them later). I bounce enough\nbetween RxJS and classic C# ReactiveX that I find these utility extension\nmethods handy to keep around to save my poor memory some small bit of\ntrouble:</p>\n<pre><code class=\"language-cs\">public static class ObservableExtensions\n{\n    public static IObservable&lt;T&gt; Share&lt;T&gt;(this IObservable&lt;T&gt; source) =&gt;\n        source.Publish().RefCount();\n\n    public static IObservable&lt;T&gt; ShareReplay&lt;T&gt;(\n        this IObservable&lt;T&gt; source,\n        int bufferCount) =&gt;\n        source.Replay(bufferCount).RefCount();\n}\n</code></pre>\n<p>The RxJS names are little more evocative to me than remembering when to\nuse <code>RefCount()</code>.</p>\n<h1><code>ISlice&lt;State&gt;</code></h1>\n<p>Redux best practices are to use a single Store to collect your entire\napplication state. This allows for easier debugging serialization and\ncentralization of some forms of developer tooling.</p>\n<p>But you often don't want to reason with an entire application state all\nat once. You are often working on a specific vertical feature and want\nto write your Reducers and Epics (more on those later) from the\nperspective of only a <em>slice</em> of your Store.</p>\n<p>We start with the general interface design:</p>\n<pre><code class=\"language-cs\">public interface ISlice&lt;State&gt;\n{\n    public IObservable&lt;State&gt; States { get; }\n}\n\npublic interface ISubSlice&lt;ParentState&gt; : IReducer&lt;ParentState&gt;\n{\n    public void RegisterParentSlice(ISlice&lt;ParentState&gt; parent);\n}\n</code></pre>\n<p><code>ISubSlice&lt;ParentState&gt;</code> is the stranger of the two interfaces in this\ndesign because it is designed to facilitate the dependency injection of\ncircular references. Each Slice can be seen as a large reducer in its\nown parent. Each Slice also needs a reference to its parent to be able\nto do that slicing work of outputting the subset of states relevant to\nthe slice.</p>\n<p>The implementation of Slice follows from this dependency injection trick\nplus one other simple one: the .NET DI container like many DI containers\nsupports injecting <code>IEnumerable&lt;T&gt;</code> to get all implementations of an\ninterface that have been injected.</p>\n<pre><code class=\"language-cs\">public class Slice&lt;ParentState, State&gt;\n    : ISlice&lt;State&gt;, ISubSlice&lt;ParentState&gt;, IDisposable\n{\n    private readonly IEnumerable&lt;IReducer&lt;State&gt;&gt; reducers;\n    private readonly Lens&lt;ParentState, State&gt; lens;\n    private readonly BehaviorSubject&lt;ISlice&lt;ParentState&gt;?&gt; parentSlice = new(null);\n\n    public IObservable&lt;State&gt; States =&gt; parentSlice\n        .Select(parent =&gt; parent is null\n            ? Observable.Empty&lt;State&gt;()\n            : parent.States\n                .Select(lens.Get)\n                .DistinctUntilChanged())\n        .Switch()\n        .ShareReplay(1);\n\n    public void RegisterParentSlice(ISlice&lt;ParentState&gt; parent) =&gt;\n        parentSlice.Next(parent);\n\n    public Slice(\n        IEnumerable&lt;IReducer&lt;State&gt;&gt; reducers,\n        Lens&lt;ParentState, State&gt; lens\n    )\n    {\n        this.reducers = reducers;\n        this.lens = lens;\n        foreach (var reducer in this.reducers)\n        {\n            if (reducer is ISubSlice&lt;State&gt; subslice)\n            {\n                subslice.RegisterParentSlice(this);\n            }\n        }\n    }\n\n    public ParentState Handle(RootAction action, ParentState parent) =&gt;\n        lens.Set(\n            reducers.Aggregate(lens.Get(parent), (acc, reducer) =&gt;\n                reducer.Handle(action, acc)),\n            parent);\n        // or with an Update helper implemented:\n        // lens.Update(parent, parentState =&gt;\n        //   reducers.Aggregate(parentState, (acc, reducer) =&gt;\n        //     reducer.Handle(action, acc)))\n\n    public void Dispose()\n    {\n        GC.SuppressFinalize(this);\n        parentSlice.Dispose();\n    }\n}\n\npublic static class SliceExtensions\n{\n    public static IServiceCollection AddSlice&lt;ParentState, State&gt;(\n        Lens&lt;ParentState, State&gt; lens) =&gt; services\n            .AddSingleton(provider =&gt; new Slice(\n                provider.GetServices&lt;IReducer&lt;State&gt;&gt;(),\n                lens))\n            // allow direct injection IObservable&lt;State&gt;\n            .AddSingleton(provider =&gt; provider\n                .GetRequiredService&lt;Slice&lt;ParentState, State&gt;&gt;()\n                .States)\n            .AddSingleton&lt;ISlice&lt;State&gt;&gt;(provider =&gt; provider\n                .GetRequiredService&lt;Slice&lt;ParentState, State&gt;&gt;())\n            // register for pickup by its parent\n            .AddSingleton&lt;IReducer&lt;ParentState&gt;&gt;(provider =&gt; provider\n                .GetRequiredService&lt;Slice&lt;ParentState, State&gt;&gt;());\n}\n</code></pre>\n<p>Our <code>CoolFeatureState</code> might be part of a larger <code>RootState</code>:</p>\n<pre><code class=\"language-cs\">public record RootState(CoolFeatureState CoolFeature)\n{\n    public static readonly RootState Initial = new(CoolFeatureState.Disabled);\n\n    public static readonly Lens&lt;RootState, CoolFeatureState&gt; CoolFeatureLens =&gt;\n        new(state =&gt; state.CoolFeature, state =&gt; coolFeature =&gt; state with\n        {\n            CoolFeature = coolFeature,\n        });\n}\n</code></pre>\n<p>With Slice implemented we can fill out our example <code>CoolFeatureExtensions</code>\nwith the next registration piece:</p>\n<pre><code class=\"language-cs\">public static class CoolFeatureExtensions\n{\n    public static IServiceCollection AddCoolFeature(this IServiceCollection services) =&gt; services\n        .AddTransient&lt;IReducer&lt;CoolFeatureState&gt;, CoolFeatureInitializedReducer&gt;()\n        .AddSlice(RootState.CoolFeatureLens);\n}\n</code></pre>\n<p>Note that both generic parameters to <code>AddSlice</code> are cleanly picked up from\n<code>Lens&lt;A, B&gt;</code> matching those parameters.</p>\n<h1><code>Store</code></h1>\n<p>I think the implementation of our central Redux Store follows pretty\ndirectly from our Slice implementation above:</p>\n<pre><code class=\"language-cs\">public IActions\n{\n    public IObservable&lt;RootAction&gt; Actions;\n}\n\npublic IDispatch\n{\n    public void Dispatch(RootAction action);\n}\n\npublic sealed class Store\n    : ISlice&lt;RootState&gt;, IActions, IDispatch, IDisposable\n{\n    private readonly IEnumerable&lt;IReducer&lt;RootState&gt;&gt; reducers;\n    private readonly Subject&lt;RootAction&gt; actions = new();\n    private readonly BehaviorSubject&lt;RootState&gt; states = new(RootState.Initial);\n\n    public IObservable&lt;RootState&gt; States =&gt; states;\n    public IObservable&lt;RootAction&gt; Actions =&gt; actions;\n\n    public Store(IEnumerable&lt;IReducer&lt;RootState&gt;&gt; reducers)\n    {\n        this.reducers = reducers;\n        foreach (var reducer in reducers)\n        {\n            if (reducer is ISubSlice&lt;RootState&gt; subslice)\n            {\n                subslice.RegisterParentSlice(this);\n            }\n        }\n    }\n\n    public void Dispatch(RootAction action)\n    {\n        var nextState = reducers.Aggregate(states.Value, (acc, reducer) =&gt;\n            reducer.Handle(action, acc))\n        if (nextState != states.Value)\n        {\n            states.OnNext(nextState);\n        }\n        actions.OnNext(action);\n    }\n\n    public void IDispose()\n    {\n        GC.SuppressFinalize(this);\n        states.Dispose();\n        actions.Dispose();\n    }\n}\n\npublic static class StoreExtensions\n{\n    public static IServiceCollection AddStore(this IServiceCollection services) =&gt; services\n        .AddCoolFeature()\n        .AddSingleton&lt;Store&gt;()\n        .AddSingleton(provider =&gt; provider.GetRequiredService&lt;Store&gt;().States)\n        .AddSingleton&lt;ISlice&lt;RootState&gt;&gt;(provider =&gt; provider.GetRequiredService&lt;Store&gt;())\n        .AddSingleton(provider =&gt; provider.GetRequiredService&lt;Store&gt;().Actions)\n        .AddSingleton&lt;IActions&gt;(provider =&gt; provider.GetRequiredService&lt;Store&gt;())\n        .AddSingleton&lt;IDispatch&gt;(provider =&gt; provider.GetRequiredService&lt;Store&gt;());\n}\n</code></pre>\n<h1><code>IEpic&lt;State&gt;</code></h1>\n<p>The final part to this pattern that does a surprising amount of heavy\nlifting is the concept of an Epic. Named after &quot;lengthy story&quot;, an Epic\nis the asynchronous workflow relative of the Reducer. Where Reducers\nmust be synchronous, the Epic can do anything Observables can do,\nincluding calling <code>async/await</code> methods and running all sorts of side\neffects.</p>\n<p>Generally Epics observe Actions, the current state after an Action, and\nreturn Actions.</p>\n<pre><code class=\"language-cs\">public interface IEpic&lt;State&gt;\n{\n    public string Name { get; }\n\n    public IObservable&lt;RootAction&gt; Create(\n        IObservable&lt;RootAction&gt; actions,\n        IObservable&lt;State&gt; states,\n        IScheduler scheduler);\n}\n</code></pre>\n<p>The <code>IScheduler</code> is explicitly provided to encourage testing with\n<code>TestScheduler</code> and also because we found reasons at runtime to inject\nspecific schedulers without the Epic needing to be aware which.</p>\n<p>The <code>Name</code> is useful for logging and debugging.</p>\n<p>An example &quot;Cool Feature&quot; Epic might look something like:</p>\n<pre><code class=\"language-cs\">public record CoolFeatureLoaded(IEnumerable&lt;CoolThing&gt; CoolThings)\n    : CoolFeatureAction;\n\npublic class CoolFeatureLoadedReducer : Reducer&lt;CoolFeatureLoaded, CoolFeatureState&gt;\n{\n    public CoolFeatureState Reduce(\n        CoolFeatureLoaded action,\n        CoolFeatureState state\n    ) =&gt;\n        state with { CoolThings = action.CoolThings };\n}\n\n/// &lt;summary&gt;\n/// When Cool Feature has initialized, load any applicable CoolThings.\n/// &lt;/summary&gt;\npublic class CoolThingLoader(ICoolThingService service)\n    : IEpic&lt;CoolFeatureState&gt;\n{\n    public string Name =&gt; nameof(CoolThingLoader);\n\n    public IObservable&lt;RootAction&gt; Create(\n        IObservable&lt;RootAction&gt; actions,\n        IObservable&lt;CoolFeatureState&gt; states,\n        IScheduler scheduler\n    ) =&gt;\n        actions.OfType&lt;CoolFeatureInitialized&gt;()\n            // Sometimes a .WithLatestFrom() is useful here if the\n            // reducer calculates something, say in this case the\n            // API wants the calculated CoolStarted time. Actions are\n            // the &quot;transaction model&quot; for the Store, so the state\n            // after an Action is observed should always reflect all of\n            // the Reducers have run.\n            .Select(action =&gt; action.IsCool\n                ? Observable.FromAsync(async (ct) =&gt;\n                    new CoolFeatureLoaded(\n                        await service.LoadCoolThings(ct)),\n                    scheduler)\n                : Observable.Return(new CoolFeatureLoaded([])))\n            .Switch();\n}\n\n// updated registration\npublic static class CoolFeatureExtensions\n{\n    public static IServiceCollection AddCoolFeature(this IServiceCollection services) =&gt; services\n        .AddTransient&lt;IReducer&lt;CoolFeatureState&gt;, CoolFeatureInitializedReducer&gt;()\n        .AddTransient&lt;IReducer&lt;CoolFeatureState&gt;, CoolFeatureLoadedReducer&gt;()\n        .AddTransient&lt;IEpic&lt;CoolFeatureState&gt;, CoolThingLoader&gt;()\n        .AddSlice(RootState.CoolFeatureLens);\n}\n</code></pre>\n<h2>Responsibility Digression about Epics</h2>\n<p>One thing that should be obvious from the Epic interface is that an Epic\nmay return the same Action it observes in the first place. This is a\nrecipe for an infinite loop.</p>\n<p>The key wisdom when writing an Epic is that often attributed to\nSpider-Man's Uncle Ben:</p>\n<blockquote>\n<p>With great power comes great responsibility.</p>\n</blockquote>\n<p>Loops are a key flow control power in software development. Just as you\nmight use a <code>while</code> loop, you may find needs for Action loops in your\nEpics. Just like working with a <code>while</code> loop, you need to make sure that\nyour loop condition is solid enough to avoid infinite loops, and you want\nto make sure that your loop has all the other fast exits it may need.</p>\n<h1><code>EpicHost&lt;State&gt;</code></h1>\n<p>The last big piece of pattern is initializing all the Epics at the right\npoint in application startup. This effort is left to a simple class\nknown as the <code>EpicHost&lt;State&gt;</code>.</p>\n<pre><code class=\"language-cs\">public interface IEpicHost\n{\n    public void Initialize();\n    public IEnumerable&lt;string&gt; EpicNames { get; }\n}\n\npublic class EpicHost&lt;State&gt;(\n    ILogger&lt;EpicHost&lt;State&gt;&gt; logger,\n    IEnumerable&lt;IEpic&lt;State&gt;&gt; epics,\n    IAction action,\n    IDispatch dispatch,\n    ISlice&lt;State&gt; state)\n    : IEpicHost, IDisposable\n{\n    private bool initialized;\n    private ISubscription? subscription;\n    private IScheduler scheduler = Scheduler.Default;\n\n    public IEnumerable&lt;string&gt; EpicNames =&gt; epics.Select(epic =&gt; epic.Name);\n    \n    private IObservable&lt;RootAction&gt; CreateEpic(IEpic&lt;State&gt; epic)\n    {\n        logger.LogDebug(&quot;Epic {Epic} starting in {Host}&quot;, epic.Name, GetType().Name);\n        return epic.Create(action.Actions, state.States, scheduler)\n            .Catch(error =&gt;\n            {\n                logger.LogError(error, &quot;Error in epic {Epic}&quot;, epic.Name);\n                // Restart:\n                return CreateEpic(epic);\n                // NOTE: You might want to include a backoff strategy for\n                // restarts or other additional debugging tools here.\n            });\n    }\n\n    private IObservable&lt;RootAction&gt; CreateEpics() =&gt;\n        Observable.Merge(epics.Select(CreateEpic));\n\n    public void Initialize()\n    {\n        if (initialized)\n        {\n            return;\n        }\n        initialized = true;\n        subscription = CreateEpics()\n            .Subscribe(\n                action =&gt; dispatch.Dispatch(action),\n                error =&gt; logger.LogError(error, &quot;Error in {Host}&quot;, GetType().Name),\n                () =&gt; logger.LogError(&quot;{Host} completed&quot;, GetType().Name),\n            );\n    }\n\n    public void Dispose()\n    {\n        GC.SuppressFinalize(this);\n        subscription?.Dispose();\n    }\n}\n\npublic static class EpicHostExtensions\n{\n    public static IServiceCollection AddEpicHost&lt;State&gt;(\n        this IServiceCollection services\n    ) =&gt; services\n        .AddSingleton&lt;IEpicHost, EpicHost&lt;State&gt;&gt;();\n}\n</code></pre>\n<p><code>EpicNames</code> can be handy for debugging and testing.</p>\n<p>Somewhere in your application startup path where it makes the most sense:</p>\n<pre><code class=\"language-cs\">var epicHosts = services.GetServices&lt;IEpicHost&gt;(); // or constructor injection\nforeach (var host in epicHosts)\n{\n    host.Initialize();\n}\n</code></pre>\n<p>To finish up our Cool Feature example registration, it's final shape is:</p>\n<pre><code class=\"language-cs\">public static class CoolFeatureExtensions\n{\n    public static IServiceCollection AddCoolFeature(this IServiceCollection services) =&gt; services\n        .AddTransient&lt;IReducer&lt;CoolFeatureState&gt;, CoolFeatureInitializedReducer&gt;()\n        .AddTransient&lt;IReducer&lt;CoolFeatureState&gt;, CoolFeatureLoadedReducer&gt;()\n        .AddTransient&lt;IEpic&lt;CoolFeatureState&gt;, CoolThingLoader&gt;()\n        .AddSlice(RootState.CoolFeatureLens)\n        .AddEpicHost&lt;CoolFeatureState&gt;();\n}\n</code></pre>\n<p>(You might want to inject an Epic Host for the <code>RootState</code> as well.)</p>\n<h1>Bonus: DI Registration Tests</h1>\n<p>I found it useful to test that all Reducers and Epics were registered\nin DI to avoid forgetting to register one (and then being confused why\nit wasn't working).</p>\n<p>It needs this helper function for Reflection:</p>\n<pre><code class=\"language-cs\">public Func&lt;Type, bool&gt; ImplementsGenericInterface(Type interface)\n{\n    var genericInterface = interface.GetGenericTypeDefinition();\n    return (Type targetType) =&gt; targetType\n        .GetInterfaces()\n        .Any(x =&gt; x.IsGenericInterface &amp;&amp; x.GetGenericTypeDefinition() == genericInterface\n            || x.GetInterfaces().Where(i =&gt; i.IsGenericInterface).Select(i =&gt; i.GetGenericTypeDefinition()).Contains(genericInterface));\n}\n</code></pre>\n<p>With that in hand in a test context where you can test your appropriately\nregistered DI container, you should be able to use tests that look\nsomething like (modulo your test framework and assertion framework\ndifferences):</p>\n<pre><code class=\"language-cs\">[Fact]\npublic void ReducersAreRegistered()\n{\n    var isReducer = ImplementsGenericInterface(typeof(IReducer&lt;&gt;));\n    var reducers = typeof(Store).Assembly.GetTypes()\n        .Where(t =&gt; !t.IsAbstract &amp;&amp; t != typeof(Slice&lt;&gt;) &amp;&amp; isReducer(t));\n    foreach (var reducer in reducers)\n    {\n        Assert.IsTrue(\n            Services.Any(desc =&gt; desc.ImplementationType == reducer),\n            $&quot;Reducer {reducer.FullName} should be registered&quot;);\n    }\n}\n\n[Fact]\npublic void EpicsAreRegistered()\n{\n    var isEpic = ImplementsGenericInterface(typeof(IEpic&lt;&gt;));\n    var epics = typeof(Store).Assembly.GetTypes()\n        .Where(t =&gt; !t.IsAbstract &amp;&amp; isEpic(t));\n    foreach (var epic in epics)\n    {\n        Assert.IsTrue(\n            Services.Any(desc =&gt; desc.ImplementationType == epic),\n            $&quot;Epic {epic.FullName} should be registered&quot;);\n    }\n}\n</code></pre>\n<h1>A Pattern for Redux with Epics in C# Dependency Injection</h1>\n<p>I think this pattern should feel built out of a lot of simple parts.\nI feel like most of the work in this particular pattern is just the DI\n&quot;glue&quot; to connect all the small lego parts of the pattern together.</p>\n<p>I've had good success with the Epic Pattern for Redux in JS and I think\nwe saw some good use of this pattern in the C# project we were using this\npattern for (the view states and much of the business logic of a frontend\nbuilt in Blazor WASM).</p>\n<p>The reliance of C# Dependency Injection in the way this pattern uses it\nI think helps keep individual pieces small, focused, and easy to unit\ntest. I wrote a lot of Reducers and Epics in this pattern, with high test\ncoverage, and I appreciated it.</p>\n<p>Given how much of the pattern seems to me to be most &quot;DI glue&quot; and\nI couldn't see enough of a case to convert it to a C# library, I thought\nit may still be useful to at least blog the pattern for posterity, as it\nmay be useful to other projects. In so far as this is copy and pasteable\ncode rather than an uncopyrightable generic description of a software\npattern, I believe the MIT license applies (no warranty, especially).</p>\n","date_published":"2026-04-07T00:00:00.000Z"},{"id":"https://blog.worldmaker.net/2025/11/23/tiny-paychecks/","url":"https://blog.worldmaker.net/2025/11/23/tiny-paychecks/","title":"Tiny Little Game Developer Paychecks","content_html":"<p>About a year ago, I put together a small add-on for Fantasy Grounds to\nadd a <a href=\"https://forge.fantasygrounds.com/shop/items/1836/view\">metric calendar</a> (or Stardate calendar) to the extensible calendar\nsystem of the virtual tabletop (VTT) <a href=\"https://www.fantasygrounds.com/\">Fantasy Grounds</a> because I was\ncontemplating an attempt to GM a one-off session, maybe two, for &quot;Star\nTrek Adventures&quot; and thought it would be nice to include Stardate logs\nin the campaign's calendar.</p>\n<p>The <a href=\"https://github.com/WorldMaker/fg-stardate\">metric calendar addon is open source</a> and you can pick up a\ncopy for free from its releases, or build it yourself with the attached\nbuild script, if you want to save a (literal) quarter or see what add-on\ndevelopment looks like for Fantasy Grounds.</p>\n<p>I put it on the Fantasy Grounds Forge for a quarter, and out of every\nquarter spent on it I get roughly 15 cents. After about a year and change\nof it being on the Forge I can safely say that I've made &quot;candy bar&quot; money\nfrom sales of it. It amuses me each time one of these payments arrives,\nthey truly are tiny, but it also makes me happy that scratching my own\nlittle itch resulted in enough side income pay to buy a whole candy bar.</p>\n<p>After many decades of failed or unfinished projects, I suppose that I'm\nofficially now a &quot;professional game developer&quot; having received candy bar\nmoney for at least one effort. (That also amuses me about this.)</p>\n<p>As far as I can tell I'm one of the few Fantasy Grounds add-on developers\nusing source control and source control automation, so I think I've now\npromised at least a small article/guide on doing that, to help that\ncommunity take some of their efforts to the next level. I would like to\ndo that for them.</p>\n<p>Also, if you haven't yet tried Fantasy Grounds yet, I can recommend it\nas a nice VTT for many game systems. Just recently\n<a href=\"https://www.youtube.com/watch?v=o27nuFjwgTM\">Fantasy Grounds became free-to-play</a> (YouTube announcement video),\nfocusing on a business model of selling the books for game systems over\nthe software itself. I think it's got some advantages over other VTTs\nbecause it is &quot;offline-first&quot; rather than &quot;cloud-native&quot;. It's\nUnity-based game client copies everything needed to play locally and a\nGM can use it offline or even allow play over a LAN disconnected from\nthe internet (as long as everyone's client versions are synchronized).\n(Not that I've attempted that yet, but I may in the future. I like that\nit is possible.) Of course you don't have to take my word for it, it is\ncheaper than ever to start trying out Fantasy Grounds, and of course I'm\na little biased because Fantasy Grounds has now made me enough side\nincome to buy a candy bar. I'd love to see them continue to succeed in a\never more competitive VTT market.</p>\n","date_published":"2025-11-23T00:00:00.000Z"},{"id":"https://blog.worldmaker.net/2025/04/27/book-club/","url":"https://blog.worldmaker.net/2025/04/27/book-club/","title":"Some of the Surprising Complexities of Voting on Books","content_html":"<p>I offered to write a new voting tool for my favorite <a href=\"https://worldmaker.net/jocobookclub/\">book club</a>. It\nseemed a good use of winter doldrums and new tools. I had several ideas\nI thought would be good for the club. I wanted another, easily open source\nportfolio site for <a href=\"https://worldmaker.net/butterfloat/\">Butterfloat</a> and to try to show off some of the\ntechniques I've been concerned with in my professional life that aren't\nalways obvious because most of that code lives (and dies) behind closed\ndoors. I had a couple other pieces of technology I wanted to play with,\neither to learn them better or because they were cheap new options or\nboth.</p>\n<p>I got further along in this nights and weekends hobby project than I\nexpected I would, especially as someone who has had to explicitly take a\n&quot;no moonlighting&quot; stance for most of my career in a needed boundary to\navoid burnout. Some of that is the new tools at my disposal. Some of\nthat is because this project turned out to be fun in some surprising\nways.</p>\n<p>I've taken technical liberties in how almost everything worked, but it's\nalso been interesting after the major efforts were done seeking &quot;Product\nOwner approval&quot; from the current book club admin (who is not me). I\ndidn't want to take over club admin, I wanted to try to work to make\nthis something friendly for an existing admin to work with.</p>\n<h2>With a Little Help from My (Artificial) Friends</h2>\n<p>While I kept talking about wanting to build a cool new voting site for\nmy favorite book club, I was rubbing up against my own boundaries on &quot;No\nMoonlighting&quot;. As users have been starting to use and settle into the\nnew site, I have been making a lot of jokes that this site is especially\nbrought to them thanks to my Junior Developers on this project: GitHub\nCopilot and Good Scotch.</p>\n<p>This is one of the best kinds of jokes because it is surprisingly true.\nThere's a style of LLM coding that has been called <a href=\"https://en.wikipedia.org/wiki/Vibe_coding\">vibe coding</a>. One\nJanuary weekend I realized I had a fun vibe coding relative of my own\nwithin this project. My Mastodon subject line (or the Welsh spelling\nCWbject if you prefer) was even &quot;<a href=\"https://smeap.com/@max/113824187346382324\">Weird Vibes (Software Engineering)</a>&quot;,\nconvergent evolution at work in describing the difference in a new\nworkflow. (I was not aware of &quot;vibe coding&quot; at the time, but now it's\nsuch a common term.) My vibe was different from the one making the rounds\nas the titular &quot;vibe coding&quot;. That January day I realized that my vibe\nwas sort of distinctly &quot;bougie&quot; in a fun way. I realized that I'd spent\nmost of that coding session feeling as if I was &quot;leaned back&quot;, in a nice\nbath robe, a rocks glass of Scotch in hand, code reviewing the output of\nGitHub Copilot, much more than &quot;leaned in&quot; and directly writing code as\nI would normally be on a day job project.</p>\n<p>I loved that vibe of leaned back, bougie code review enough that I was\nimmediately joking about starting a new consultancy focused entirely on\nbeing &quot;What if Masterpiece Theater was about Code Reviews?&quot; I still kind\nof think that would be a cool company. I don't know how much of a market\nthere would be for &quot;We rent nice mahogany offices, set up a good whiskey\nbar, wear cool robes and smoking jackets, and only review code, we don't\nwrite it&quot;, but if you are an investor looking for that opportunity, hit\nme up, I've got ideas.</p>\n<p>Moonlighting has less risk of burnout when it feels like distinctly\ndifferent vibes from day job work. My current day job does pay for GitHub\nCopilot on my projects, but isn't currently encouraging me to work with\na good scotch or bourbon in hand. Also most of the things I'm working on\nat my day job aren't necessarily easy off the shelf algorithms and &quot;basic\nCRUD&quot; in the same way that this hobby project has been.</p>\n<h2>The Schulze (or &quot;beatpath&quot;) Method and Rabbit Holes of Voting System Knowledge</h2>\n<p>A lot of us get interesting hyperfixations at various times that stick\nwith us. One of my college ones was getting deep in trivia of Robert's\nRules of Order, where I generated half an idea that I wanted to build a\nmeeting presentation tool more tuned for parliamentary procedure than\nPowerPoint. (That is maybe a fun idea to revisit with LLMs, though the\nultimate crash of that project was not wanting to get into the licensing\ndrama of modern Robert's Rules and not expecting the tool to be that\nexciting for running contemporary meetings if sticking only to the\ncontent of Public Domain versions). Related to that one, and both a\ndeeper rabbit hole and maybe more important to following years, was a\ndeep dive into the more arcane mathematics, game theory/economics, and\nsoftware algorithms related to many of the ways it is possible to\ncalculate the votes of a group to find the most interesting winner.</p>\n<p>The most common voting systems in our lives are all, in one way or\nanother, &quot;first-past-the-post&quot; systems where a simple majority winner\ntakes all. The very well known failure cases of those systems that are\n&quot;first-past-the-post&quot; are what leads to common modern problems like &quot;two\nparty systems&quot; and ugly compromises like slate voting and &quot;never vote\nfor a third party even if that's really what you want because that 'takes\naway' votes from the next best candidate in one of the only two 'allowed'\nparties&quot;.</p>\n<p>There are lots of ways to solve this, but there's lots more ways that\nfeel like they solve this but are really just\n&quot;first-past-the-post-with-more-steps&quot;. Ranked choice (rank some or all\nthe candidates from favorite to least favorite) often give the feeling\nof solving this easily, with relatively &quot;easy&quot; ways to assign &quot;points&quot;\nand build point systems that do easy math in a spreadsheet. No offense\nto anyone that likes to run a voting system like that or how much\ninteresting work has gone into running such votes and tweaking point\nsystems, but the ease of doing that is deceptive that in how rare it can\nbe for your &quot;points&quot; system to help solve things like let the winner be\nthe candidate that would win the most head-to-head battles against all\nof the other candidates.</p>\n<p>The nerdy mathematical name for this goal that head-to-head battles\nmatter is the <a href=\"https://en.wikipedia.org/wiki/Condorcet_winner_criterion\">Condorcet winner criterion</a> and you might be surprised\nhow many voting systems fail this criterion. It sounds like an easy\nthing to do, but it's a lot harder in practice, mathematically, than it\nsounds. (Also, I would be technically remiss if I didn't point out that\nthe Condorcet winner criterion is not the only possible criterion to\nseparate a &quot;good&quot; voting system from a &quot;first-pass-the-post&quot; one, there\nare several &quot;competing&quot; criteria that have different trade-offs. I like\nthe Condorcet criterion best, because it allows for &quot;surprising&quot; winners\nbut winners that most voters can still agree should have won, which is\nto say it is very good at avoiding &quot;two party systems&quot; where there's\nonly two choices and the rest is &quot;throwing your vote away&quot; and &quot;spoiling&quot;\na loser's chance.)</p>\n<p>I became one of those nerds with a favorite voting system. That system\nis generally referred to as the <a href=\"https://en.wikipedia.org/wiki/Schulze_method\">Schulze method</a>, or the &quot;beatpath&quot;\nmethod. (It does pass the Condorcet winner criterion.) The Schulze\nmethod is a ranked pairs system (rank all candidates versus each other\nin head-to-head battles) that mathematically accepts and flourishes with\nties so it can be <em>presented</em> as if it were a simpler ranked choice\nsystem which allows ties. I think this is a surprisingly big deal: no\none really wants to vote for every pair of choices (think &quot;Round Robin\ntournament&quot;), but present them a list of candidates to rank every one of\nthem 1-5 stars like they are writing Yelp Reviews and they can &quot;secretly&quot;\ndo all the work of doing a full pairwise ranking, with interesting ties,\nand have fun doing it.</p>\n<p>Unfortunately the Schulze method is easier demonstrated than described,\nespecially the math behind it. For a while there was a cool website called\n<a href=\"https://modernballots.com/\">Modern Ballots</a>, may it rest in peace, that I could point to do sample\nvotes. It was rather close to a &quot;Survey Monkey to make quick Schulze\nballots&quot;. With that website gone (but obviously not forgotten) it has\ngotten a lot harder again to convince people to try Schulze method\nvoting. The math is just hard enough that no one wants to particularly\ndo it by hand (I don't), and it isn't easily illustrated how to do it in\nan Excel sheet or Google Sheet, either. But the math is also so juicily\neasy for a simple program to automate. The meat of the Schulze method\nuses a slight variation on a simple textbook algorithm called the\n<a href=\"https://en.wikipedia.org/wiki/Floyd%E2%80%93Warshall_algorithm\">Floyd-Warshall algorithm</a>. It is one of those algorithms you learn about\nearly in an undergraduate study of computing, because it also has deep\nearly computing roots from the time when &quot;Dynamic Programming&quot; meant\n&quot;solving a problem in place in its own existing data structure&quot; rather\nthan something more exciting than that.</p>\n<p>It's one of those algorithms that you may code one or two times for class\nassignments and wonder if you'd ever actually need it in the real world.\nIt's one of those algorithms that code competitions love to include as\nnon-obvious solution to a word problem that then turns into a quickly\nsolved &quot;known algorithm&quot; project after you think long and hard about it.\nThe Floyd-Warshall algorithm is for finding the &quot;widest path&quot; in a\ndirected graph (digraph). Very simply: you've got paths from places like\nA to B and A to C and C to B. These paths have numbers on them for some\nreason that often is a variation of &quot;how wide is this path&quot; (how many\npeople can walk it side by side, how much money does it cost to pay the\ntolls, how slow is it on a busy traffic day, all sorts of other\nword problem variations like that). Is it a wider path to go directly\nfrom A to B, or should you take the scenic route from A to C to B? These\nsorts of questions are surprisingly common, so that undergraduate\nimplication of &quot;you should learn this because it is handy&quot; turns out to\nbe a real world thing sometimes.</p>\n<p>The nickname for the Schulze method as the &quot;beatpath&quot; method comes\ndirectly from this core reliance on the Floyd-Warshall algorithm: you\nare looking for the widest path of candidates, no matter how convoluted,\nwho beats the most other candidates, in whatever surprising order the\ngraph returns. If you've encouraged a lot ties along the way, as in using\na simple, strict 1-5 &quot;stars&quot; choice, sometimes the widest paths are very\nsurprising, but in a fun way (and a Condorcet criterion way) that few can\nargue with the results.</p>\n<p>One of the reasons that this hobby project turned into &quot;bougie vibes\ncoding&quot; for me was entirely because the hardest mathematical piece of\nthe puzzle was very much writing a comment that I was about to use the\nFloyd-Warshall algorithm adapted for the Schulze method and allowing\nGitHub Copilot to spit out almost exactly the Wikipedia definition of\nthe Floyd-Warshall adjusted from Wikipedian pseudo-code to the language\nI was actually working in with some of my coding style. I took the time\nto review that it actually matched the definition and my code style, but\nI was in a place to be very pleased that something I had written a bunch\nof times before (as mentioned) was so easily plugged in for me here by\n&quot;my Junior Developer&quot;, Copilot. Not all programming is &quot;use this very\nwell known algorithm in this rather well known use case&quot; (I'd argue most\nisn't), but it was certainly exciting to see an example play out directly\nhere, and code review it in a bathrobe with a good Scotch in hand.</p>\n<h2>Passkeys Are So Nearly (Sigh) the Present</h2>\n<p>Years ago after too many near misses on database leaks and other concerns,\nI made a decision that I never wanted to be in charge of storing passwords\nin a database ever again. I hate passwords, personally. I especially hate\nthe feeling of the risk of someone's one-and-only password becoming\ncompromised due to my &quot;silly fun hobby project&quot;.  I've been advocating to\nget rid of them in one way or another for many years. I was a fan of the\noriginal Blogger's friend version of OpenID and was sad to see it devolve\ninto related-in-name-only standards like OpenID Connect. I was a\nproponent for [Mozilla Persona] and still remain disappointed it didn't\nsucceed. (One of my few other moonlighting projects back in the day\nthat's fun to compare/contrast with this book club site was one that\n<a href=\"https://github.com/WorldMaker/StingerCheck/\">used Schulze voting and Mozilla Persona</a>. It died when Rotten Tomatoes\nshut down their public API almost exactly a year into running that site,\nbut if not for that it would have died soon after that when Mozilla\nPersona was shutdown.)</p>\n<p>Passkeys are the current hope for the present of &quot;no passwords in my\ndatabase&quot;. It feels like they are ready for prime time and the mainstream,\nwe just need a bit more education, a bit more pomp and circumstance, a\ntiny bit more polish in edge cases. It feels like we finally have a big\nenough multi-vendor coalition it isn't destined to die like Mozilla\nPersona did. Technically, it even feels like a slightly more polished\nversion of what Mozilla Persona wanted to be when it grew up (a way for\nwebsites to get an ID directly from the user's browser with no middle\nmen), though it is missing some of the things that made Mozilla Persona\nso nice to work with. A big one for me is that Mozilla Persona was\ndesigned with the intent that there be an easy to verify claim of a\nuser's email address directly associated with the key/ID. As someone that\ndoesn't want to pay for a transactional email provider for my hobby\nproject, I would love for an email attestation on Passkeys to be something\neasy to request and also to verify with a trustworthy third party. I also\nunderstand why that's not currently in anyone's Passkey plans (and may\nhave played a part of why cross-vendor interest in Mozilla Persona was\nso low).</p>\n<p>This site was my first attempt to implement Passkeys as a so-called, in\nsecurity jargon, Relying Party (RP). I used an off-the-shelf library for\nthis called <a href=\"https://simplewebauthn.dev/\">simplewebauthn</a> and hit some issues where despite the name,\nit did not feel as simple as I would have liked. This is partly because\nsimplewebauthn, to stay simple, acts mostly as a lego kit with a gnarly\npack of step-by-step instructions, including many &quot;DIY Here&quot; steps, and\na wave for good luck. To my benefit, I haven't been doing anything\nparticularly exciting or different or weird with it, and I appreciated\nit as a lego kit for not constraining my choice of front-end &quot;Frameworks&quot;\nfor my front end (as I'd already made my choice, which I will get to\nlater in this post), even as I kept hoping for an even simpler solution.</p>\n<p>As much as I could complain about how many headaches it gave me in the\nthick of choosing it and writing the code to glue it all together and\nimplement it, a lot of it really was just following the lego instructions,\nand a lot of that did benefit from &quot;let Copilot write the first pass&quot; and\nthen clean up its assumptions and fix things specific to my backend and\nfrontend choices. Not exactly the same &quot;lean back&quot; experience of writing\nthe core voting algorithm but something similar feeling to that. This was\nthe first big project of the application (if you can't login, how can you\nvote) before even building the voting code, and a lot of the\nprocrastination leading into the project was not wanting to build this\ncode in the first place. I'm glad I got through it, and I think I did a\nstrong job with the strange intricacies of logging in with Passkeys and\nonly Passkeys, as well as the bootstrap phase to register an account's\nfirst Passkey. I can't say it is the most secure implementation (and\ngiven a lack of transactional email provider to actually verify emails,\nit certainly isn't), but it is also possibly overkill for a &quot;silly fun\nhobby project&quot;. Yet I succeeded in not storing anything even resembling\na user password in the site's database.</p>\n<p>Passkeys were one of my biggest anxieties in technical choices leading\ninto the &quot;MVP&quot; demo with the Product Owner. I was pleasantly surprised\nat how well it demoed and the overall acceptance of it. Though I also\nwas lucky here that the Product Owner's background in information\nsecurity also easily agreed &quot;oh yeah, no passwords&quot;.</p>\n<p>I also expected a lot more user support/training issues and/or complaints\nwith Passkeys and have been mostly pleasantly surprised with general user\nacceptance. Again here some of that is probably the luck of the bias of\nthis book club in question to have a somewhat tech friendly background\noverall.</p>\n<p>The biggest issue seemed to be an old (already out of security support)\nversion of macOS claiming to support Passkeys but failing to register a\nnew one in any browser using the system keychain. The workaround seemed\nto be to register on a recent enough iOS device and well enough I'm told\nthe Mac eventually synced that key and worked with it.</p>\n<p>The next biggest issue has been Windows 10 which is in a similar place of\n&quot;supports Passkey&quot; but has quirks with it and only syncs with Windows\ndevices. I had planned for this issue, as my own main development machine\non this project is stuck with Windows 10, and so I made sure that my\nimplementation supported registering multiple keys for the same email\naddress (which I've always taken for as table stakes in Passkey\nimplementation, but it's interesting how some still don't).</p>\n<p>We also found out that the &quot;the Facebook (embedded) browser&quot; is generally\nblocked from using Passkeys and/or doesn't implement Passkey support on\nevery phone OS. This has been particularly frustrating because sharing\nlinks on Facebook has been common for the book club, which has used an\nFB Group as a central communications channel since it started. It's easy\nto forget that not everyone distrusts the embedded browsers in platforms\nlike Facebook or even understands the difference between opening a link\nin an embedded browser and opening the link in the system browser/their\nregular and default browser. Facebook doesn't help this by moving the\n&quot;open in default browser&quot; option strangely hard to find. (Today it's\nbehind an ellipsis menu. Who knows where it will move tomorrow.)</p>\n<p>I also tried to mitigate some feedback ahead of time by suggesting\nlogging in with an iOS or Android device first, because those have the\nsubtly strongest implementations, are slightly more likely to be somewhat\nup to date (given mobile OS update policies and cell carrier enforcement\nof some of them), and generally the best sync behavior to their\nrespective ecosystems. Windows 11 can do the QR code dance to login with\nan iOS or Android Passkey then help you register a Windows Passkey. Some\nLinux setups can do that now, too. I got some feedback on the earliest\nwording of that suggestion that I was making it sound like the website\n<em>only</em> worked on iOS or Android, and I was happy to reword that and also\nstill feel like I'm trying to find the best way to word that advice\n(without also over-explaining it, as I do here).</p>\n<p>A lesser pet peeve I have with Passkey UX is that I’ve implemented all\nthe markup for the best “autofill” experiences but it doesn’t light up\nand I believe the reason for that is that in most browsers it still\nassumes a password field. I’ve wondered if adding a dummy password field\nmight help browsers show the best UX, but that seems silly for a\nPasskey-only website to do and I certainly don’t want to confuse my users\nwith a vestigial password field that doesn’t do anything just to autofill\ntheir email address with a cute key icon next to it. I hope the browsers\nimprove the UX for “no password” sites, as much as I understand why the\nUX is maybe overly focused on the chicken-and-egg bootstrap dance of\nupgrading sites that still use “traditional” passwords first.</p>\n<p>Of course, some of this user acceptance feedback still feels like &quot;early\nadopter&quot; feedback and maybe I should still brace for more pain in future\nregistration waves as our least technical users find time to want to vote\nor we find new club members with more diversity in their technical\nbackgrounds. But overall I think big takeaways are beware old Passkey\nimplementations that only sort of work, know your workarounds for that\n(allow registering the same email twice; always support multiple Passkeys\nin an account), and I wish there more examples of Passkey-only sites in\nthe open source zeitgeist to double check implementations on. Hopefully\nthis implementation will be another one of use to someone else next (even\nif indirectly through better GitHub Copilot vibe coding, maybe).</p>\n<h2>Web Components with Butterfloat</h2>\n<p>I wanted another public, open source portfolio project for <a href=\"https://worldmaker.net/butterfloat/\">Butterfloat</a>.\nI'm still quite proud of Butterfloat and I know it isn't a &quot;Framework&quot; on\nhardly anyone else's radar (and maybe can't be because it isn't churning\nthrough backwards compatibility breaks fast enough 😼), but one of those\nthings that if I build cool things with it maybe I slowly convince more\npeople to try it. In particular, both of the other public sites that are\nopen source in my portfolio were migrated from Knockout and were single\npages (though one because it was built to be single-page-application-like\nand the other only because it was a small demo with no need for a second\npage). This site I knew I wanted an old school multi-page app, because I\nwanted to use a static site generator to build as much as possible ahead\nof time. I also knew going into this project that I wanted to start from\nthe perspective of a (&quot;traditional&quot; for a static site generator [SSG])\n&quot;flat file database&quot; of Markdown files in folders (with some modest YAML).</p>\n<p>I've had some ideas for building an SSG with Butterfloat, but this project\ndidn't feel right for experimenting with those ideas. Particularly with\nthe desire to use the Markdown files with frontmatter paradigm. I did\nhave fun discovering <a href=\"https://lume.land/\">Lume</a> as a minimalist SSG with all the basics\ncovered that I expected and needed.</p>\n<p>This seemed like the right project where I needed to finally test building\nout Web Components with Butterfloat in a classic multi-page architecture.</p>\n<p>Overall, I've been very excited from the results of building web\ncomponents with Butterfloat. I've mentioned many times that a guide star\nfor Butterfloat has been &quot;modern Knockout&quot; and it has been in building\nthese web components that I've felt some of the most like I've been\nhonoring the Knockout legacy. Knockout was critical in the early\n&quot;Progressive Enhancement&quot; web, and web components, when they work well,\nhave a beautiful way of feeling like the endgame of Progressive\nEnhancement. Simple DOM elements get replaced with more interesting things\nif JS is available and as soon as it loads. That feels a lot like the best\nof Knockout's experiences in the old days. Having the ability to do it\nwith much less of a &quot;flash of unstyled content&quot; is a strong improvement.\nWeb component elements themselves have no default content or styles and\nif you place things like &quot;noscript&quot; warnings inside them it is a quick\nmatter to replace them on web component startup. Additionally, template\ntags are better than what Knockout was doing with programming inside\ncomments and hiding things with display CSS at runtime. <a href=\"https://worldmaker.net/butterfloat/#/stamps\">Stamps</a>\n(&quot;server-side rendering&quot; of Butterfloat static DOM to template tags) have\nbeen in Butterfloat for some time now, but Stamps definitely shine in the\ncontext of web components, building template tags at “compile time” ready\nfor web components to pick up as soon as they are ready.</p>\n<p>Going into building web components with Butterfloat I was worried that it\nwas going to be more complex and/or harder than it turned out to be. Given\nsome of the other web component libraries I've seen for other\n“frameworks”, I expected to need a bunch of custom adapter code or things\nof that nature, but I think the Butterfloat component model and lifecycle\nsort of accidentally turned out to be perfect for running inside web\ncomponents and the amount of code to build a Web Component from a\nButterfloat Component seems almost too simple enough to me. I've\n<a href=\"https://worldmaker.net/butterfloat/#/guides/web-components\">documented the bones of the pattern</a> already and hope it helps other\nprojects looking for a lightweight alternative for a Web Component\n&quot;framework&quot;.</p>\n<p>Of course, to be fair, part of why this book club site has had such a\nsimple time with Web Components is that I intentionally eschewed the\nShadow DOM and there's nary a Shadow Root in sight anywhere in the\nproject. Turns out that is something that you <em>can just do</em>. I know a lot\nof Web Components tutorials and discussions get very deep into the weeds\nof the Shadow DOM, and I understand why so many libraries that build Web\nComponents may see needs for Shadow DOM tools, but also I think a lot of\nthe over-focus on the Shadow DOM does injustice to how simple they are\nwithout it, and how much you can do with Web Components that don't have\nShadow DOM. But also, I'm a fan of letting CSS do what it does best at,\nat a global level across the page, because that is a lot of power and the\nShadow DOM is partly about distrusting page styles rather than taking\nadvantage of them. I do like to choose to take advantage of them.</p>\n<p>Building Butterfloat components with GitHub Copilot has been fascinating.\nObviously Copilot is trained on a ton of JSX from React projects. For the\nmost part a lot of that just works, though stylistically it’s nice to also\nupdate it for shortcuts Butterfloat supports that React doesn’t (like\n<code>class</code> over <code>className</code>). In a couple places Copilot has been useful in\nhelping me find React idioms that didn’t work in Butterfloat but could\n(and was quickly upgraded to support) and ones that I still intentionally\ndid not wish to support preferring more Butterfloat-specific idioms. This\nwas also a fun case of watching Copilot pick up more and more of those\nButterfloat specifics from this project as it grew and presumably also\nfrom rich code search of my other public and open source Butterfloat\nprojects.</p>\n<p>My biggest pet peeve with building these web components is that the ESM\nnative, properly tree-shakeable version of <a href=\"https://rxjs.dev/\">RxJS</a> is apparently currently\ntrapped behind waiting for various standards organization working groups,\nbecause the current maintainers want to wait for “Signals” and possibly\nbrowser-native Observables proposals to shake out first. I understand the\nreasoning behind that (align to standards for the next SemVer major\nrelease), but I don’t agree with it, because we’ve been on this\nmerry-go-round before with standards bodies almost doing native\nObservables and then giving up after lots of hemming and hawing and giant\ndebates. The fact that this round also includes trying to standardize\nObservables-but-dumber &quot;Signals&quot; drama doesn’t give me a lot of confidence\nthat things will turn out better this time than the last time, and in the\nmean time as great as esbuild is, I still would love to get real\ntreeshaking from RxJS (Butterfloat’s one and only dependency).</p>\n<h2>Deno KV and Over-Engineering a Vote Engine for a Larger Scale than Necessary</h2>\n<p>For this project the backend database I chose was <a href=\"https://docs.deno.com/deploy/kv/manual/\">Deno KV</a> on\n<a href=\"https://docs.deno.com/deploy/manual/\">Deno Deploy</a>. I evaluated a lot of &quot;serverless&quot; deployment tools and\ntheir various database backends. There's a lot of great options today.\nThere are a lot of options with interesting marketing budgets and &quot;fan\nbases&quot;. I started exploring Deno earlier when I made sure that Butterfloat\ngot a <a href=\"https://jsr.io/@worldmaker/butterfloat/score\">good score on JSR</a> and found I liked a lot of the philosophy and\ndeveloper experience feel of it. (So much so I’m debating making\nButterfloat Deno and JSR-first and suggesting that over traditional npm\ninstalls, but I’m not in a rush to do that.)</p>\n<p>I'd pretty happily recommend Deno Deploy at this point. So far the\ndeveloper experience has been great.</p>\n<p>Deno's Deploy product on paper seems to have fewer features that most of\nthe more popular/hyped options. It's primary database is &quot;just&quot; a &quot;simple&quot;\nkey-value store without a lot of bells or whistles like some of the hosted\nSQL databases and/or &quot;NoSQL&quot; document databases that are &quot;standards&quot; in\nthis area.</p>\n<p>But Deno Deploy's own focus on its documentation spoke to me. The\nexperiences with JSR and other parts of the Deno ecosystem have all been\npleasant and I think the Deno team seems to show a lot of maturity in how\nthey think about developer experiences. That focus on documentation means\ntheir website leads relatively straight to the developer documentation,\nrather than some of other hosting providers trying to steamroll you\nthrough marketing hype after marketing hype page or blind trial sign-up\nactions without first being able to read the developer document. (It also\nhelps that Deno Deploy seems to have a very generous free tier compared\nto some of its peers.) A lot Deno's documentation alone makes up for how\nrelatively &quot;young&quot; Deno Deploy is and how many parts of it are still\nappropriately and visibly labeled &quot;experimental&quot; or &quot;unstable&quot;, which\nshows maturity, to me at least, in how everything is documented. It's\ncounter-intuitive but being able to clearly see the &quot;experimental&quot; and\n&quot;unstable&quot; labels helps my impression a lot. To some extent a lot of\nthese &quot;serverless&quot; hosts still feel like <em>most</em> of their products should\nbe labeled &quot;experimental&quot; or &quot;unstable&quot; and Deno admitting to it feels\nmore mature to me.</p>\n<p>As for Deno KV, I also knew from past experience that the border between\n&quot;just&quot; a key-value store and &quot;a document database&quot; is really blurry,\nespecially if you don't mind doing a little bit of work up front on your\n&quot;primary indexes&quot; (your key building patterns) and rolling your own\n&quot;secondary indexes&quot; as needed. I especially loved that Deno KV brings its\nown invisible &quot;path separator&quot; for its key namespaces. This makes it\neasier to build smart &quot;primary indexes&quot; while also providing tools to\navoid some of the obvious problems like key injection attacks.</p>\n<p>There is a lot you can do with &quot;just&quot; a KV, especially if you are willing\nto over-engineer things a bit. I’ve certainly picked up familiarity over\nthe years from complex usages of things like redis caches and Local\nStorage.</p>\n<p>I'm quite proud of the voting engine in this book club site. It's designed\nfor a massive scale that this particular club doesn't really need, but it\nwas exciting to write it that way, and it is also leads to more than a\nbit of &quot;penny pinching&quot; in interesting ways to help keep the site within\nDeno's current generous free tier.</p>\n<p>In this case the Schulze method is very amenable to a classic &quot;map/reduce&quot;\npattern, with each ballot being mapped to an adjacency matrix describing\nthe &quot;beats&quot; graph for that user, reducing those adjacency graphs through\na simple matrix sum aggregate, then taking the last summation matrix and\nmapping that through the Floyd-Warshall algorithm to arrive at the final\nmatrix of the widest paths of all the votes.</p>\n<p>The voting site backend is using <a href=\"https://docs.deno.com/deploy/kv/manual/queue_overview/\">Deno Queues</a> to orchestrate all of that\nwork. Additionally, the users' ballots are partitioned into (currently)\n32 random buckets (but flexibly more as needed) reducing the number of\nadjacency matrixes that need to be resummed when a single user votes, as\na simple complexity reduction to what we call <code>O(log n)</code>. I'm proud of\nthis bucketing, which is accomplished through a bit of maybe silly but\nhandy &quot;primary index&quot; magic of using for the ballot keys the reversed\nstring of the user's ID for the ballot. User IDs are <a href=\"https://github.com/ulid/spec\">ULIDs</a> which are\ntimestamp up front and random entropy at the back, so reversing the ID\ngives random buckets (as opposed to time insertion buckets, which is\nuseful in other cases for things like clustered indexes and log-oriented\nappends/merges). (String reversals of these basic 32-letter alphabet ASCII\nstrings are also easily reversable and/or repeatable, making it still easy\nto do ballot lookups by User ID.) As far as hash bucketing schemes go,\nit's not a very complex one, but it works well in this case.</p>\n<h2>&quot;Offline First&quot; for the MPA World</h2>\n<p>One of the things I’ve picked up from past SPAs that I’ve worked on is the\nimportance for designing for &quot;offline first&quot;. Some of the applications I\nworked on for past jobs <em>demanded</em> &quot;offline first&quot; architectures, because\nif you are in the field examining some remote reach of a river, you\naren’t likely to have good cell service no matter how close we feel to\nhaving ubiquitous cell service coverage in the US.</p>\n<p>But the thing I found from &quot;offline first&quot; is that it generally &quot;feels\nbetter&quot; and that it often feels like how modern apps seem that they are\nsupposed to feel.</p>\n<p>All of the CRUD (create, read, update, delete) work in the voting site is\ndesigned to be &quot;offline first&quot;, using Local Storage generously and some\nvery simple &quot;three-way merge&quot; techniques. (CRDTs are fun, but not what I\nthought was needed because ballots are intentionally single user.) This\n&quot;offline first&quot; approach may seem especially like overkill for a\nmulti-page application because &quot;the next page&quot; isn’t likely to load when\noffline, but I think people are surprised how well MPA applications get\ncached and even when you are not intentionally expecting users to come to\nthe site while offline, the experience of becoming accidentally offline\nor even just needing to &quot;save a draft until I come back&quot; is fantastic if\nyou've designed for &quot;offline first&quot;, I think. Because offline happens\nunexpectedly all the time and people often want time to draft things and\ncome back. In a multi-page application it especially helps to give that\n&quot;feel&quot; of a single page application, being able to make changes in one\npage and reflect them in a second, without actually being a single page\napplication.</p>\n<p>(Between Butterfloat components caching well, the speedy loading of\nStamp-based components, and the fast loading from local storage of\n&quot;offline first&quot; data I’ve even heard surprised remarks that some users\nthought it was a very well optimized single-page application. I’m sure\nif I add CSS view transitions that illusion will feel complete. I may add\nCSS view transitions. As others are also saying, it’s a good time to start\nwriting MPAs again. I do recommend considering &quot;offline first&quot; as a useful\ntool even for an MPA.)</p>\n<h2>Takeaways and Action Items</h2>\n<p>I’m still skeptical about the long term viability of LLMs in software\nengineering. It’s not going to replace most of what I do and I’m not the\nsort to “vibe code” entire hobby projects (much less professional\nprojects) because I’m still me and saddled with a goal to over-engineer\nfor scale and reliability beyond the bare minimum, but hobby projects\nstart to be something I want to do again when I can lean back on a cold\nwinter’s day with a Scotch and code review some moron Junior Developer\nthat’s great at copypasta and Stack Overflow and Wikipedia cribbing\nsolutions. It makes the real engineering easier when the grunt work is\ndone so quickly. It’s nice to have &quot;a team&quot; for solo projects now. That\nalso helps hobby work feel more like &quot;senior-level&quot; work: explaining to\njunior developers what to do is a good chunk of my day jobs and Copilot\ntakes instructions in similar (though not the same) ways.</p>\n<p>I've got a couple wishlist items out of this project:</p>\n<ul>\n<li>I’d love for browsers to improve Passkey-only site UX</li>\n<li>RxJS needs a good scoring “JSR build” in 2025 and the wait for it is\nsilly</li>\n<li>Facebook really should add a way to link to sites explicitly in the\nsystem's default browser, especially if OS-level Passkeys don't work\nin the embedded browser</li>\n</ul>\n<p>I think there’s some useful Action Items for other projects coming out\nof this one:</p>\n<ul>\n<li>Don’t underestimate the power of an MPA design in 2025</li>\n<li>“Progressive Enhancement” is back and better than ever with Web Components</li>\n<li>Try Butterfloat, it’s great and lightweight (single dependency! great DX!)</li>\n<li>Passkeys are here now and mostly great</li>\n<li>“Offline first” is great for everyone, even MPAs</li>\n<li>The Schulze (“beatpath”) method is a great way to vote on silly things\nlike “next book to read” but also maybe serious things, and more people\nshould be aware of it</li>\n</ul>\n<p %=\"\" post_url=\"\" 2014-05-26-mozilla-persona-and-owin-part-2-supporting-bearer-=\"\" %=\"\">[Mozilla Persona]:</p>\n","date_published":"2025-04-27T00:00:00.000Z"},{"id":"https://blog.worldmaker.net/2024/11/17/butterfloat-stamps/","url":"https://blog.worldmaker.net/2024/11/17/butterfloat-stamps/","title":"Butterfloat 1.5.0: Stamps for Static Rendering","content_html":"<p><a href=\"https://worldmaker.net/butterfloat/\">Butterfloat</a> released a big 1.5.0 with a big new building block\n(nicknamed <a href=\"https://worldmaker.net/butterfloat/#/stamps\">Stamps</a>) for server-side rendering and static ahead-of-time\nrendering for the static HTML parts of components. This meets a bunch of\nunder-spoken needs for JS-based view engines, fulfills a big early\npromise of Butterfloat, and does it with hopefully style and convenience.</p>\n<h2>The Promise of Progressive Enhancement</h2>\n<p>My guiding star for Butterfloat from the beginning was &quot;Modern Knockout&quot;.\nThere's influences from React, and lessons learned from Angular's mess,\nbut I felt like there was also some things missing that Knockout was\ngreat at which have been missing for a while.</p>\n<p>One of the biggest of those, for me, was how Knockout was built during\nand somewhat epitomized (for me, at least) the somewhat now broken\npromises of the now passed &quot;Progressive Enhancement&quot; era.</p>\n<p>I had made a promise to myself to include some &quot;Progressive Enhancement&quot;\ntools in Butterfloat and enshrined it in the issue tracker as the first\nand most important issue. It was something I prepared for in the base\n&quot;DNA&quot; of the design. It just didn't make the &quot;MVP&quot; cut of the original\nrelease.</p>\n<p>I'm excited that Butterfloat 1.5.0 fulfills that promise for the first\ntime with Stamps. (It's an early &quot;anniversary&quot; gift, almost exactly a\nyear after the first release.)</p>\n<h2>The Need for Progressive Enhancement Never Left</h2>\n<p>One of the prods that pushed me to think now was a great time to deliver\non a promise of &quot;Progressive Enhancement&quot; were multiple calls to do it\nin recent news and reports. Several great articles have been written in\nrecent months on the accessibility problems created by the &quot;modern\nframeworks&quot; of today's React and Angular for users on cheap devices and\nbad connections. Because these sorts of frameworks have become the\n&quot;default&quot; or the &quot;best practice&quot; or the &quot;easiest road&quot; in so many places,\nthey've become the tools in use even in places that <em>should</em> be\nprioritizing accessibility.</p>\n<p>I don't think Butterfloat will single-handedly fix the anti-trend, but\nhaving tools for &quot;progressive enhancement&quot; was still important to me and\nthe time is always &quot;now&quot; and I appreciated the reminder that there were\ngood reasons to do it to help people that need it. Even if Butterfloat\nmight not be the most common choice to help solve that, I hope it helps\nat least one other developer to have another option to point to that can\ndo it and that looks good.</p>\n<h2>Keeping it Easy</h2>\n<p>The Butterfloat component model was already focused on making static\nHTML obviously distinct from change bindings (via Observables). Due to\nthis you can build a Stamp from any easily testable Component (and most\nComponents can be easily testable). There's no difference between a\nComponent that supports Stamps and one that doesn't. Stamps are always\nbindable at runtime, there's no need to choose between interactivity and\nstatic building a Stamp, there's no architecture/strategy/pattern like\n&quot;Islands&quot; to need to rewrite to. It's truly &quot;progressive enhancement&quot;.</p>\n<p>Stamps continue the theme of taking more modern approaches than the\nstate of the art from the Knockout days. Where Knockout used a DSL\nwritten in comments and sometimes showed a flash of incomplete content\nwhile the library was loading. Stamps by default build into modern\n<code>&lt;template&gt;</code> and <code>&lt;slot&gt;</code> tags. They won't be rendered at all in modern\nbrowsers until instantiated or unless &quot;prestamped&quot; into the container.</p>\n<h2>More is Possible</h2>\n<p>I keep referring to Stamps as just the &quot;low level&quot; building block for\nprogressive enhancement/SSR/SSG. There are still more ideas to explore\nfrom them as a starting point:</p>\n<ul>\n<li>Because Stamps build to <code>&lt;template&gt;</code> and <code>&lt;slot&gt;</code>, there may be good\nscenarios to enable rendering them via the &quot;Shadow DOM&quot; rather than\nbinding them with Butterfloat.</li>\n<li>There may be more room for writing Butterfloat components &quot;Stamp-first&quot;.\nStamp bindings are not intended to be edited by hand, but they follow\npatterns that <em>may</em> be amenable to future tooling.</li>\n<li>Stamps currently exist entirely within Component boundaries. It would\nbe nice to have tools to merge stamps and encompass larger trees of\ncomponents in a single Stamp. That would open up more static site\ngeneration (SSG) opportunities.</li>\n<li>I've long been meaning to explore Observable completion as a tool for\n&quot;binding removal&quot; in SSG.</li>\n<li>Internally Butterfloat wires many things using &quot;anonymous&quot; Components\nat runtime. It is not currently that useful to run an entire Butterfloat\napp only with Stamps and treeshake out the static DOM builder. (Though\nthis does not save that many KBs.) It might be nice to explore solutions\nto that. Especially for ideas of trying to run Web Components as just\nButterfloat Stamps and try to treeshake golf the components.</li>\n</ul>\n<p>I don't know when I'll prioritize any of that work, given at the moment\nthis is mostly a &quot;free time&quot; project (though a Production-ready one, if\nI may say so). I'd love contributors if any of the above ideas or others\nspark interest.</p>\n","date_published":"2024-11-17T00:00:00.000Z"},{"id":"https://blog.worldmaker.net/2024/03/13/node-packaging/","url":"https://blog.worldmaker.net/2024/03/13/node-packaging/","title":"Modern Node Packaging and Bundler-Free Living","content_html":"<p>I've found myself giving multiple light tutorials on how simple modern\nES Module-only packaging can be in Node/npm packages. I've similarly\ngiven several tutorials on deploying web apps &quot;bundler-free&quot; (it's\nmore possible than many think). There's a lot of outdated advice on\nthese subjects, and some interesting superstitions, so I thought it\nwould be useful to blog about the state of things in 2024 as I know\nthem and hopefully update some out of date advice with helpful advice\nthat you can be lazier and things may be simpler than you think and a\nlot of the confusion is because of outdated advice.</p>\n<p>I'll get into details at length, and some digressions along the way,\nbut I think the top-level summary is simple and worth bullet pointing\nup front. If you are looking to build, bundle, and/or package projects\nfor any or all of Node, npm, bundlers, and/or the browser, things are\nmuch simpler if you only build/bundle/package ES Modules:</p>\n<ul>\n<li>You only need <code>&quot;type&quot;: &quot;module&quot;</code> and <code>&quot;exports&quot;</code> in <code>package.json</code></li>\n<li>No <code>&quot;main&quot;</code>, only <code>&quot;exports&quot;</code>, but <code>&quot;exports&quot;</code> <em>can be</em> as simple as\n<code>&quot;main&quot;</code> was</li>\n<li>Every tool that understands <code>&quot;exports&quot;</code> understands <code>&quot;type&quot;: &quot;module&quot;</code></li>\n<li>Use only the <code>.js</code> file extension</li>\n<li>Use <code>.js</code> file extensions for relative imports</li>\n<li>Aside: Consider mocha or <code>node --test</code> for unit testing</li>\n<li>You might not need a bundler (or you may just need less bundling\nthan you think)</li>\n<li>Aside: You don't need Babel anymore</li>\n</ul>\n<h2><code>&quot;type&quot;: &quot;module&quot;</code></h2>\n<p>The simplest thing to do when going ES Module only is to add a\n<code>&quot;type&quot;: &quot;module&quot;</code> to your <code>package.json</code>. That does most of the work.\nIt says all <code>.js</code> files are ES Modules. You mostly only need the <code>.js</code>\nfile extension for everything at that point, so long as everything is\nmodules and you <em>want</em> everything to be modules. You can forget entirely\nyou ever heard the file extension <code>.mjs</code> and you can mostly avoid ever\nneeding a <code>.cjs</code> file and that only for increasingly rare developer\ndependencies.</p>\n<p><code>&quot;type&quot;: &quot;module&quot;</code> works in all currently security supported Node\nversions. It works for a few versions back out of security support,\ntoo, now.</p>\n<p>In your package itself: You don't need to build any CommonJS files. You\ndon't need to build any AMD or UMD files. Let everything be ES Modules.\nIn your development processes CommonJS should be rare to non-existent\nand can use the <code>.cjs</code> file extension.</p>\n<p>Yes, you need to have the <code>.js</code> file extension on all your relative\nfile imports. The browser expects filenames to be specific and include\nthe file extension. Deno and Bun both require it. Most recent versions\nof Node in <code>&quot;type&quot;: &quot;module&quot;</code> packages also require it, though there's\nstill a few ways that slips through the cracks. It's only some bundlers\nthat don't require it today.</p>\n<h3>Typescript</h3>\n<p>I love Typescript. Typescript is great. Just set Typescript to target\nES Modules, and you only need to target ES Modules. Just drop the JS\nfiles in place (the default build configuration) and include the <code>.js</code>\nfile extension in all your relative imports. (Yes, <code>.js</code> not <code>.ts</code>.\nTypescript decided it was better not to transform this and to emit it\nas-is, so it wants you to use <code>.js</code> just like Node or the Browser\nexpects at runtime.) Typescript tries to auto-detect if you are using\nfile extensions and will start adding the <code>.js</code> for you in its\nrefactoring/auto-complete added auto-imports once you do it enough.</p>\n<p>My advice lately is that a reasonably good combo (per caniuse\nstatistics) for <code>&quot;module&quot;</code> and <code>&quot;target&quot;</code> in your <code>tsconfig.json</code> is\n<code>es${new Date().Year - 2}</code>. I've been using <code>&quot;es2022&quot;</code> lately for both.</p>\n<h3>Exception: eslint</h3>\n<p>The only CommonJS file I find I need today is the .eslintrc.cjs file.\nIt's the only place in my projects recently that you will see a CommonJS\nfile at all, and certainly the only need I have for the <code>.cjs</code>\nextension. Because it is the only exception I currently have, it feels\nworth naming and shaming the one tool that doesn't yet support ESM\nthat I like to use. That looks to finally be fixed in upcoming eslint 9.</p>\n<h3>Aside: Testing</h3>\n<p>Jest still calls ES Module loading &quot;experimental&quot; and has weird bugs\nabout it. Karma is terrible. (Node is already a v8 instance, you don't\nneed to boot another v8 instance from your v8 instance just to run\nunit tests. That's not unit testing, that's some weird relative of\nintegration testing.) I recommend mocha or <code>node --test</code>. Mocha is\nancient (older than jest), and minimalist, but was also one of the\nfirst harnesses to hand ES Modules well. (Sometimes the old dogs learn\nnew tricks better, I suppose.) <code>node --test</code> is the new kid on the\nblock, new enough many developers haven't yet discovered it. It's\ngreat to have a test harness built-in to Node. That's one fewer\ndependency you don't need. It's similarly minimalist to mocha, but\nslowly gaining features, too. You can import all the asserts you need\nfrom <code>'node:assert/strict'</code> and the <code>describe</code>/<code>it</code> test suite setup\nfunctions from <code>'node:test'</code>.</p>\n<h2><code>&quot;exports&quot;: &quot;./main.js&quot;</code></h2>\n<p><code>&quot;type&quot;: &quot;module&quot;</code> does most of the work at runtime, but if you are\npackaging you need <code>&quot;exports&quot;</code> in your <code>package.json</code>. Don't include\n<code>&quot;main&quot;</code>. (There are no bundlers that understand <code>&quot;exports&quot;</code> but not\n<code>&quot;type&quot;: &quot;module&quot;</code>. There are bundlers that see <code>&quot;main&quot;</code> and\naccidentally or intentionally ignore <code>&quot;type&quot;: &quot;module&quot;</code>.)</p>\n<p><code>&quot;exports&quot;</code> has a bad reputation for being complicated and confusing. It\ncan be as simple as <code>&quot;main&quot;</code> if you let it: <code>&quot;exports&quot;: &quot;./index.js&quot;</code>.</p>\n<p>The requirements to use simple <code>&quot;exports&quot;</code> are basically everything this\nguide is about: ES Modules only, with <code>&quot;type&quot;: &quot;module&quot;</code>, and one &quot;front\ndoor&quot; for &quot;bare&quot; Node package imports.</p>\n<p>The useful difference between <code>&quot;main&quot;</code> and <code>&quot;exports&quot;</code> is that\n&quot;front-door&quot; only approach. With <code>&quot;main&quot;</code> Node would still let you\nimport from any .js file in the package and with <code>&quot;exports&quot;</code> you only\nget the declared exports and nothing else, no more &quot;sneaking in the\nback-door&quot;. This is useful as a package author for properly\nestablishing your public API. This is useful as a library consumer for\nmaking importmaps easier to build (less accidentally importing\nsub-files after the well known &quot;bare&quot; imports).</p>\n<p>Even if you do need more than one &quot;front-door&quot;, you can still use a\nsimpler form of <code>&quot;exports&quot;</code> and ignore all the suggestions regarding\n<code>&quot;import&quot;</code>, <code>&quot;require&quot;</code>, and <code>&quot;typings&quot;</code> subkeys:</p>\n<pre><code class=\"language-json\">{\n    &quot;exports&quot;: {\n        &quot;.&quot;: &quot;./index.js&quot;,\n        &quot;./optional-cool-thing&quot;: &quot;./cool.js&quot;\n    }\n}\n</code></pre>\n<p>Typescript since 4.7 has no problems picking up typings from packages\nthat include the TS sources or obviously named <code>.d.ts</code> files for any\n<code>&quot;exports&quot;</code> configuration, including these simple ones, so long as files\nare simply predictably side-by-side.</p>\n<h2>Bundler-Free Living</h2>\n<p>You might not need a bundler anymore, at least in development, but\nsometimes even in Production you need less bundling than you think in\n2024.</p>\n<p>You can use an importmap to import &quot;bare&quot; Node package names in the\nbrowser.</p>\n<p>ES Modules work great in today's browsers with <code>&lt;script type=&quot;module&quot;&gt;</code>.</p>\n<p>In a growing number of the simplest cases you can just prune and ship\n<code>node_modules</code> to a web server, with an appropriate importmap.\n<code>npm prune --omit=dev</code> will remove all of your development\ndependencies. You may need to spot check for large files like test data\nto also prune.</p>\n<p>You may need to bundle some of your dependencies, especially ones that\ndon't yet ship ES Modules or haven't yet adjusted to including .js file\nextensions for browser compatibility. You can bundle one dependency at\na time. &quot;Vendorizing&quot; your dependencies like this can be a one-line\ncommand with a bundler that itself outputs ES Modules. I use esbuild\nfor this with the <code>--format esm</code> flag, it's generally a simple one-line\nCLI command that I can include in <code>package.json</code> <code>&quot;scripts&quot;</code> and/or\ndocumentation.</p>\n<p>ES Modules loaded as ES Modules is a great development experience in\nthe browsers. You may not even miss &quot;hot reloading&quot; (and you can try\nto implement it without the big frameworks and bundlers if you still\nwant it).</p>\n<p>In HTTP/2+ and some well configured/behaved HTTP/1.1 servers there's a\nlot less of a &quot;per-file&quot; performance hit than developers historically\nworried about. Many ES Modules libraries are collections of lots of\nsmall files, but the always asynchronous loading of\n<code>&lt;script type=&quot;module&quot;&gt;</code> combines with some optimizations in dependency\ngraph loading for smoother performance than many old pre-bundler\nmemories.</p>\n<p>You can use Browser performance tools to make bundling decisions based\non real production data. Rather than always bundle everything, see\nwhat your actual browsers and servers are doing with real world\ndependency graphs. You may find they download less overall than your\nprevious bundles. You may find that you only need to bundle specific\nsub-graphs of your dependencies as they become specific problems.</p>\n<p>You don't have to take my word on it, or any framework's big default\nbundling configuration: try it yourself and use real data. You may be\nsurprised at how many fewer reasons to bundle there are with ESM. (I\nhaven't been that surprised: I shipped AMD applications a long time\nago with very little bundling, and that was just HTTP/1.1. ESM flies\nhigher and further, especially on HTTP/2+.)</p>\n<h3>Useful Tweak: <code>&quot;sideEffects&quot;: false</code></h3>\n<p>If you package a library and expect someone (including yourself) to\nuse certain bundlers, especially Webpack, it may make sense to still\ninclude the non-standard <code>&quot;sideEffects&quot;: false</code> flag in your\n<code>package.json</code>. This suggests to Webpack (and the few others) that it\nmay tree-shake your library at its most aggressive. Many other bundlers\ndo either deeper closure parsing or trust <code>&quot;type&quot;: &quot;module&quot;</code> says you\nknow what you are doing with ES Modules and don't have CommonJS\ntransition code and synchronous requires.</p>\n<h3>Aside: So long, Babel (Thanks for All the Fish)</h3>\n<p>You don't need Babel in 2024. It was a great tool for its time. The\nnumber of polyfills and actual amount of transpiling you need for\ncompatibility in 2024 is minuscule (check caniuse statistics if you\ndon't trust me on this). If you need JSX consider using something\nlighter like Typescript and/or esbuild. Everything else you think you\nmight need is already in Node or your Browser now.</p>\n<h2>Living Example</h2>\n<p>If you want to see an example library packaged this way, with an\nencouragement to try bundler-free living (at least in development),\nand with direct documentation on these same subjects, I can offer\n<a href=\"https://github.com/WorldMaker/butterfloat/\">Butterfloat</a> as one such\nuseful open source library.</p>\n<p>The <a href=\"https://worldmaker.net/butterfloat/#/getting-started\">Butterfloat Getting Started Guide</a> includes some\nsimilar recaps to this blog post, including an example of bundler-light\nliving, showing how to spot bundle just the RxJS dependency and then\nuse an importmap for the vendorized RxJS and the <code>node_modules</code> &quot;bare&quot;\npackage import of Butterfloat to setup a very light-weight development\nenvironment.</p>\n<h2>Summary</h2>\n<p>In the ES Modules-only world of Node/npm/browsers/bundlers, everything\nshould start to be easy again. Use <code>&quot;type&quot;: &quot;module&quot;</code> and you can\nignore a lot of old advice and/or outdated confusion.</p>\n","date_published":"2024-03-13T00:00:00.000Z"},{"id":"https://blog.worldmaker.net/2023/11/28/butterfloat/","url":"https://blog.worldmaker.net/2023/11/28/butterfloat/","title":"What is Butterfloat and Why is it The Greatest?","content_html":"<p>I've been working on a modern web view engine named\n<a href=\"https://github.com/WorldMaker/butterfloat\">Butterfloat</a> (now in pre-release) that is\ninterestingly Knockout-inspired, but with a focus on Typescript and\nas pure as possible RxJS Observables. I think it has been a great\nproject, and I'm proud of the results I've gotten as a solo\ndeveloper doing it in the middle of other tasks in just a few months.</p>\n<h2>How Did We Get Here?</h2>\n<p>I've been on an interesting journey for some time now. It started\nwith me criticizing some deep architecture decisions of Angular and\nhow their wishy-washy approach to RxJS made mistakes too common and\ntoo easy. From the things that I experienced getting strong\nperformance out of an RxJS-heavy Angular application was an effort\nof futility and required fighting uphill against a lot of &quot;Angular\nBest Practices&quot; as seen in an ecosystem full of blog posts and Stack\nOverflow answers available for copy and paste.</p>\n<p>What happened next was that I hit a Production issue in Angular that\nwas precisely the sort of ghost story that developers least want to\nexperience: it was a massive, reliably repeatable performance stall\nout that debug builds could not reproduce at all, even when pointed\nto Production data and following every one of the same steps,\nmeticulously. This led me to deep diving into ways to remove Zone.js\nand many aspects of Angular's change detection that most application\ndevelopers using Angular don't seem to encounter. That led me to the\nneed to pursue a &quot;Component Framework&quot; to help build RxJS-focused\ncomponents in a way that tried to lead to a &quot;pit of success&quot; in\nterms of RxJS best practices, removing the need for Zone.js, and\ntaking a different approach to Angular change detection. I called\nthat framework Pharkas after fictional videogame pharmacologist,\nFreddy Pharkas. (Because I felt like I too was a frontier pharmacist\nslinging Rx in a lawless frontier.)</p>\n<p>I had a few people ask if I would consider taking the lessons learned\nfrom Pharkas and some of the ideas posited on how to build an\nRxJS-focused templating language and build something from scratch\nfrom that. I considered it more than once, but wasn't sure that\nwould be an itch I felt a need to scratch. Surely someone else might\nbuild that at some point, and generally I've been happy with React\nas &quot;good enough&quot; for most projects.</p>\n<p>In August of this year, after more than 8 years in that position, I\nwas unceremoniously tagged in a &quot;headcount reduction&quot; due to a new\nincoming CTO and an expectation of a shift in software strategies.</p>\n<p>Among the many, many tasks involved in a full time job search, I\nstarted working on one of the more unique to a software developer:\npolishing my GitHub profile and revisiting some of my public\nrepositories on GitHub. Several of mine were written in Knockout,\nwhich was the style at the time, or which was a preference of mine\nfor &quot;quick and dirty proof of concept&quot;. In dusting the cobwebs,\ndealing with the bitrot, paying down some of the tech debt of the\n<a href=\"https://github.com/WorldMaker/compradprog\">CompRadProg</a> demo, in particular, I was thinking that\nit might be great to upgrade it to something modern. The more I was\nlooking at it though, the more I was torn by how &quot;elegant&quot; some of\nmy View Models have been in Knockout, including in this &quot;quick and\ndirty proof of concept&quot; demo that I love to talk about but don't\nreally have much left to do with it.</p>\n<p>In thinking on this, it started to seem that the tools were in my\npower, that I should just build my own view engine with real, pure\nobservables and Typescript and otherwise mostly vanilla ES2022+. I've\ntried to stick to one major runtime dependency: RxJS. I've tried to\nstick to one major build-time dependency: Typescript. (You could\nuse Babel instead, but you probably don't need Babel. I use esbuild,\nmyself. But as just a view engine, Butterfloat in unopinionated on\nyour build tooling choices.)</p>\n<h2>How Is It Different From Most View Engines?</h2>\n<p>I think a lot of the modern web frameworks have learned from Knockout\nin some way or another. The impression I have of Angular, Vue,\nSvelte, and Qwik is that all of them were too enamored with the\n&quot;magic&quot; of Knockout's &quot;computed&quot; observables, and never quite\nlearned some of the lessons that Knockout's observables are more\naccurately Subjects in modern observable nomenclature and the leak\nof imperative concerns across API boundaries was never quite seen as\nthe problem it should have been. To me, that was often the weakness\nof large Knockout codebases and seems the continued weakness of many\nof today's Knockout successors, too.</p>\n<h2>How Is It Different From React?</h2>\n<p>This is where things start to get interesting. A simple Hello World\nexample at first glance looks a lot like an ordinary React function\ncomponent. I wanted the only compiler involved to be Typescript as\nmuch as possible, so Butterfloat makes heavy use of TSX\ninfrastructure which is already an HTML-like template engine with a\nlot of features it would take me quite a bit of effort to reinvent\nfrom true scratch. A Butterfloat Component is a function. (It's\nalways a function at this time, there's no equivalent to a React\nclass component at all.) Just as with React, it can take as a first\nargument some number of properties that reflect the &quot;attributes&quot; in\nTSX that were passed to the component.</p>\n<p>That's where things start to diverge. A Butterfloat Component takes\nan optional second argument called the Component Context. This\ncontext provides some useful helpers, which we'll get to.</p>\n<p>On top of that, a Butterfloat Component is static by default.\nButterfloat is not a Virtual DOM. In a Knockout-inspired feeling that\nmore of an application's DOM is static than not, it has no diff and\npatch mechanisms of the intermediate description language its TSX\ncompiles to. The only parts that can and will change once\ninstantiated to the DOM are things bound to Observables and other\nComponents (which of course &quot;secretly&quot; themselves become\nObservables, too, at run time).</p>\n<p>There are testing benefits to having this rich &quot;intermediate\ndescription language&quot; similar to the virtual nodes of most Virtual\nDOM engines. Some types of Butterfloat Components may be tested\nentirely in Node without a need for useful DOM faking/testing\nlibrary such as JSDOM. The descriptions can even be richer than is\ntypical in a Virtual DOM environment because Butterfloat doesn't\nexpect most of them to be long-lived or commonly created so it\ndoesn't need to try to save space by using shorter names or other\nsuch clever shortcuts. On top of that the descriptions are natively\nwritten in Typescript so benefit from some type-level distinctions\nthat you don't commonly see in Virtual DOM virtual nodes.</p>\n<p><a href=\"https://cycle.js.org/\">Cycle.js</a> is a great Virtual DOM with Observables, if that\nis what someone is looking for. Butterfloat tries to be a &quot;static\nDOM&quot; with Observables in a way that I feel like I haven't really\nseen since Knockout.</p>\n<p>One further divergence that I'm particularly proud of is that\n<code>@types/react</code> is a multi-thousand line file of seemingly\nhand-maintained types, other JSX/TSX implementations either copy\nand paste this work and hand merge it, or don't bother entirely. I\nbuilt a much fewer line bit of meta-typing on top of Typescript's\n(auto-generated) <code>lib.dom</code> types. I think I've got a <em>better</em>\ndeveloper experience than React at a fraction of the cost of\nlabor (no matter how much of that labor is volunteer work by\nDefinitelyTyped organization contributors). (One of the ways it is\nbetter: the auto-generated <code>lib.dom</code> types include MDN direct links\nin the documentation comments. I get those &quot;for free&quot; by\ninheritance.) I expect other JSX/TSX implementations to learn from\nthis example now that one of us has done it, and I keep debating if\nI want to try the political game of PRing something like it to\ngood old <code>@types/react</code> itself.</p>\n<h2>Where Did The Name Come From?</h2>\n<p>My design documents for this project for a few days were in a folder\ncalled Dr. Mario after the only other, better known, fictional\npharmacist in videogames, in direct relationship to my Pharkas\nproject. I spend those days searching for a better name. I was at a\nfootball game where I was especially thinking about how much I'd like\nto somehow honor Knockout in the name without sounding too directly\nrelated to Knockout (or its once and future intended successor Total\nKnockout). By that point I realized that the thing most center in\nthe distance that I was staring out at, mostly unfocused, while\nthinking about this project was the logo for the Louisville Muhammad\nAli International Airport. One of Muhammad Ali's well known\ncatchphrases was the classic &quot;Float like a Butterfly, Sting Like a\nBee&quot;. That seemed like a good idea for what I was going for with any\nsort of Knockout-inspired project and satisfies the boxing metaphor,\nalbeit obliquely.</p>\n<p>In the spirit of the Greatest Champion of All Time, I feel in a good\nplace to proclaim that with Pharkas I built a tool better than\n(baseline) Angular and here with Butterfloat I have built a tool\ngreater than React in the spirit of Classic React (just a web view\nengine). It is the greatest view engine for the modern web.</p>\n","date_published":"2023-11-28T00:00:00.000Z"},{"id":"https://blog.worldmaker.net/2023/11/16/prismatic-decimal-system/","url":"https://blog.worldmaker.net/2023/11/16/prismatic-decimal-system/","title":"The Prismatic Decimal System","content_html":"<p>I'd had a passive interest in finding this novel for years. It had\nbeen first mentioned to me in the footnote of an autobiography of a\nmodest local 1950s television star as a favorite novel of her\nchildhood. Though she referenced it only directly in passing in the\nflow of a larger discussion of her much beloved children's show, there\nwas a sense that perhaps this book played a larger role in some of the\ncreative spark of the show as a whole. I had a sense that there were a\nlot indirect references and in jokes to that novel in that section and\nI was curious to find out more. It seemed implied that the book was a\nwell beloved book at least regionally and the author presumed some\nfamiliarity with the contents of that book. I didn't think I could\ntell for sure without reading that novel on my own so I added it to a\nwishlist at the local free public library.</p>\n<p>The library's online records told me the history of only the last\nthree copies of the book in the system, though I was glad for whatever\nlibrarian had inputted even that much information into the digital\nsystems about a 1910s small press book from a publisher that lasted\nonly a dozen years and never really sold much outside this city.\nUnfortunately for my search the last three copies were listed as\n&quot;damaged beyond repair&quot;, &quot;sold at fundraising auction&quot;, and &quot;on\ninter-library loan; overdue for return as of 03/1972&quot;. I figured that\nwas the end of my search, but I knew enough to leave it on my wishlist\nas sometimes inter-library loans would turn back up (even ones somehow\ndecades overdue) or a librarian would spot an archived copy. What I\nthought most likely this century and given the book was on the edge of\nbeing public domain even in 1972 (given the publisher bankruptcy, and\nno known author's estate, probably extremely likely it was already\npublic domain then) sometimes a scanning group would find a copy and\nbuild an ebook. The wishlist had automatically sent me e-copies of\nother strange finds over the years.</p>\n<p>What I least expected was the handwritten letter that arrived on the\nlibrary's letterhead years after I'd even forgot placing it on my\nwishlist. It was a pleasant surprise and I was almost surprised how\nmuch I appreciated it more then than had it shown up as email.</p>\n<p>&quot;Dear Ms. Lynne,</p>\n<p>&quot;It came to my recent attention in discussing books with one Mr.\nForsberg that he has a copy of J. Y. Milburn's <em>Flight Into Night— A\nSlumbering Fantasia</em> in his private collection. I mentioned knowing at\nleast one library patron with it wishlisted as to read for several\nyears ongoing and that apparently piqued Mr. Forsberg's curiosity. Mr.\nForsberg made it clear that he does not loan books of such vintage to\nthe library or its patrons as a general policy, but that this was a\nparticularly interesting book worth loaning to a new reader, if he\nwere given the opportunity to meet with the patron, face to face,\nfirst. It's rarely good library policy to recommend engaging a private\ncollector in a direct manner such as this, but Mr. Forsberg is a close\nfriend both to me personally and to the library system as a whole and\nI promised to at least pass the offer to visit his private collection\nand maybe loan this book you've been looking for. Mr. Forsberg assured\nme that it would be worth your time to read this book and he'd love to\nmake that connection. Please find attached Mr. Forsberg's card should\nyou be interested.</p>\n<p>&quot;Regards and thank you for patronage and the time out of your day to\nread this letter,</p>\n<p>&quot;Verity Hayward&quot;</p>\n<p>I had no trouble finding the card Mx. Hayward had attached to the\nletter. &quot;X. Forsberg, Raconteur (Retired)&quot; was the dominant text on\nit, with only a phone number, what appeared to be a bare street name\nwith no other address part but the county's name beside it. I was\nweary about anyone that might call themselves a &quot;raconteur&quot;, of\ncourse, but something about that &quot;(Retired)&quot; seemed humanizing to me,\nand I wasn't sure why I felt that way.</p>\n<p>It took me a few days to decide if I would reach out to Mr. Forsberg.\nIt seemed like a lot of effort to go through just for a chance to read\na book that had been suggested to me merely by recommendation of a\nfootnote of an autobiography. But I was also never one to turn down\nany kind recommendation from a librarian, and especially not one sent\nas a handwritten note (though admittedly this was my first). I have\nalways trusted librarians as a general rule, but if you cannot trust a\nlibrarian named after &quot;Truth&quot; I'm not sure who in the world you could\ntrust, even if I don't think I've met Mx. Hayward in my tiny branch I\nmost often frequent.</p>\n<p>Pushing my courage and pressed by my curiosity on one rainy afternoon\nI finally convinced myself to call the number on Mr. Forsberg's card.\nIt took me a moment to find where I had last hidden the Phone app on\nmy home screen, having so much less need of it this century. This\nritual search added its own little needles of anxiety into the\nprocess. I was surprised when the ring was answered almost immediately\nby what sounded like a aged and yet sweetly voice, &quot;Mr. Forsberg's\nprivate residence, who may I say is calling?&quot;</p>\n<p>&quot;June Lynne, I received Mr. Forsberg's card from a librarian to\ndiscuss Milburn's 'Flight Into Night'?&quot; I didn't mean for it to be a\nquestion, but it certainly came out as one in that moment between\nnerves and hesitancy.</p>\n<p>The voice on the other end sounded excited and happy for me, &quot;Oh\nexcellent! Mr. Forsberg adores that book. Yes, Mr. Forsberg would be\ndelighted to extend you an invitation to meet with him here at his\nresidence to discuss it. Unfortunately, Mr. Forsberg keeps a very busy\nschedule so it can't entirely be at your convenience. Mr. Forsberg's\ncalendar suggests that this Saturday Afternoon say at 3 PM would work\nbest for him, would you be available?&quot;</p>\n<p>&quot;I, uh, yeah I can make that work.&quot; I did not expect things to move so\nfast to a scheduled appointment, but had to admit I was free Saturday\nnight.</p>\n<p>&quot;Excellent. I will let Mr. Forsberg know to you expect you here around\nthen. Anything else I can help you with?&quot;</p>\n<p>&quot;I'm not sure I know what the address is?&quot;</p>\n<p>There was a pause, then a hesitant, &quot;I believe it should have been on\nthe card?&quot;</p>\n<p>&quot;Oh, well there's a street name but there doesn't seem to be a street\nnumber.&quot;</p>\n<p>&quot;Yes, that's the address.&quot;</p>\n<p>That was not at all the answer I was expecting, but I was not going to\nlet my confusion trap me in a longer phone call, &quot;Alright, thank you,&quot;\nand we exchanged the usual phone conversation ending pleasantries and\nhung up.</p>\n<p>That Saturday was warm, but not too warm, and overall a lovely bright\nday. I was surprised that the maps app on my phone had no trouble\nfinding the address as I started to type it. As secretive as Mr.\nForsberg seemed to, only passing his address around on physical cards\nit seemed, he couldn't stop it from being indexed into all the usual\ndatabases. Though I appreciated that a lot in this case because I had\nno idea how I would have found the address if it hadn't been\nsearchable on the phone app, I know I've become too reliant. I guessed\nI'd try to call a librarian at that point, but I had a route and that\nwas a theory I didn't need to test.</p>\n<p>The route took me down a city highway I've driven a million times and\nyet somehow just two turns off it I entered what felt like a forest.\nThere was no room in my mental map of the city for this forest, and I\nwould imagine even a small park in this area would be something that\nI'd recall noticing on maps before. The phone's satellite imagery\nreassured me that this forest had probably been here the whole time\nand it was maybe my mental map that needed updating, but these trees\nfelt too old and wild to be contained somewhere in a city's limits.\nThe street that was the entirety of the address was a small country\ngravel road leading deeper into the forest. A country gravel road was\nalso something that didn't quite fit on my mental map of this part of\nthe city. It wasn't very far along the gravel road before it opened up\ninto a giant clearing. Here the satellite map showed a blur where the\nclearing must be, in the center of the forest and I presumed that at\nleast some privacy could be bought even in the satellite age. Between\nthat and the number of buildings that were scattered across the\nclearing I was starting to get an impression of an eccentric amount of\nwealth involved (if I'd not already been making assumptions from the\ncard and the phone call).</p>\n<p>The residence campus, and it seemed like a small college campus of\nsome strange sort, had a clear house looking building centrally beside\nthe gravel road, which seemed the most obvious place to stop and park.\nMy eyes wandered across the other buildings. They had a variety of\narchitecture styles and materials that seemed to imply an organic\ngrowth over many decades. Only a few of them seemed to offer any\ndirect hints of their use. There was one full of windows and a lot of\nfog on its windows yet greenery visible where you could see through it\nimplying a green house or conservatory of some sort. There was (if I\nwas counting the number of sides correctly) an hexagonal building with\nan intriguing glass dome atop that I clocked as the library only once\nI realized that seated on plinths flanking the entrance were\nengravings of probably Athena or perhaps Minerva, marking it a hall\nfor wisdom.</p>\n<p>I had barely mounted the steps towards the porch of main house when a\nmatronly stout woman bearing an iPad like a weapon arrived to\nintercept me from further around the porch (which appeared to wrap the\nentire house). With a deft, practiced maneuver she moved the iPad up\nand under the crook of her left shoulder, and extended her right hand\nin greetings, &quot;Ms. Lynne, I presume?&quot; I nodded as I took her hand into\nhandshake and she continued, &quot;I'm Mrs. Montgomery, Mr. Forsberg's\nChief Executive Officer, we spoke on the phone the other day. Mr.\nForsberg has decided to take tea in the garden on this delightful\nafternoon,&quot; her handshake was firm and commanding, &quot;If you will follow\nme, I will lead you to the garden.&quot; She broke the handshake,\nunholstered the iPad and glanced at it, then proceeded to lead me\naround the house.</p>\n<p>From the first moment of meeting Mr. Forsberg I had a very tough time\ndeciding if he were some sort of monster or some sort of muppet. As he\nunfolded himself from the garden chair, he kept seemingly growing\nuntil he stood above me at what should have been an inhuman height. He\nwas tall and angular, yet seemingly far too skinny for his frame, in a\nway that looked like someone had accidentally stretched a small boy\nout longwise like pulling taffy. He was possibly the ugliest man I had\never met in person, yet somehow one of the most charming as well.\nThere were hidden delights in the way his eyes glittered in the\nsunlight, and every part of him animated with more life than the\naverage person seemed to. Somehow a part of me kept being surprised by\nthat over-exaggerated animation and it's surprising grace and fluidity\ndespite a seemingly very awkward and gawky frame making the movements\nthat much more implied to me that just out of range of my eyes were\nmarionette strings or a puppeteer's arm. They say that when you meet a\nmuppet like Kermit it's very easy to get taken into the illusion and\nyou don't want to see the strings or puppeteers after only a few\nmoments working with him, and not even moments into a handshake with\nMr. Forsberg I had decided that I did not want to dispel the illusion\nand check to see if he was a muppet, even if I would constantly be\nconfused for the rest of my relationship with him that I would never\ndecide if he was more monster or muppet.</p>\n<p>Mr. Forsberg invited me to take tea with him and within moments of my\nacceptance and a light few presses by Mrs. Montgomery on that tablet\nof hers, a cook had brought me a cup of Orange Pekoe and an assortment\nof light cheeses and olives to accompany it. Somewhere in the middle\nof pleasantries such as the lovely Saturday afternoon weather and the\nbeautiful garden around us, Mr. Forsberg offered, &quot;Please, call me\nXavier if you would like,&quot; and I was charmed by it in part because of\nthe somewhat incongruous feeling knowing his first name gave me. It\ndid not strike me as the sort of first name of either a monster or a\nmuppet, and maybe not even a first name I would expect from someone so\nseemingly always formal by contagious force such as Mr. Forsberg.\nSomehow settling into calling him Xavier did not dissuade him from\nsticking to &quot;Ms. Lynne&quot; in all cases of address. It started to give me\nthe impression that &quot;Raconteur (Retired)&quot; was much less a\nself-described affectation and somehow an accidental label given him\nby some rival. I shouldn't have found it charming, but it came across\nto me as very charming and somehow natural and native to Mr. Forsberg.</p>\n<p>Eventually the general pleasantries gave way to the specifics and\nXavier asked how I’d become aware of Milburn’s “Flight Into\nNight” and I mentioned that autobiographical footnote that had lead\nme down the curious path to meeting Mr. Forsberg. Xavier nodded, and\nadmitted to having also read that autobiography. I mentioned my\nthoughts from the time I read it that perhaps Milburn’s “Flight\nInto Night” played a larger role in the context of the children’s\nshow and its origins than just a single footnote might imply. Xavier\nnodded along and then congratulated me on my “astute\nobservations”.</p>\n<p>We sat in comfortable garden white noise (some eager crickets, some\nbirds chattering sweetly among themselves). I realized I had finished\nthe available cheese and olives and my tea was finished.</p>\n<p>“Well,” Mr. Forsberg cleared the silence somewhat hesitantly, “I\ncan tell you are fellow book reader after my own heart and I would\nlove to hear your book report on Milburns’s work once you finish it.\nI’d love to give you access to my family’s library.”</p>\n<p>Xavier suddenly had a key in the palm of his hand. I did not catch it\nif he had palmed it from a jacket pocket from one of his big sweeping\ngesticulations or even perhaps if some puppeteer’s stage hand had\nplaced it there. (Though I was only somewhat disappointed the key was\nsimply a standard modern Yale lock, not some monstrous ancient cast\niron thing or something even more fantastically fairy tale.)</p>\n<p>Xavier moved to hand me the key but then paused, “I’m afraid I\nmust add one more stipulation to the terms: none of the books are to\nleave the grounds. Though you will be treated as a very welcome guest.\nYou will be free to come and go as you please. I’m sure that Mrs.\nMontgomery had already logged your vehicle into the security systems\nand will only need a head’s up in the case you plan to arrive in a\ndifferent one. She can show you how to request tea service if you wish\nfor it, and I’m sure the chefs would be happy to account for you in\nmeal planning if provided enough lead time. I’m biased of course,\nbut I believe there are many fantastic spots, hidden and not so\nhidden, to fall into a book throughout my homestead. On days such as\nthis, you might make use of this garden, of course, or there are\nhiking paths out to some lovely little grottoes and scenic points. One\nof my favorites has a surprising view of the downtown skyline. Indoors\nthere are of course study nooks throughout the library and I would be\nhappy to introduce you to some of the lesser used studies and sitting\nrooms in some of the guest buildings if you want some variety.”</p>\n<p>It was my turn to hesitate as that was a lot to take in. It certainly\nwas inconvenient, but there seemed to be far worse places to read\ninteresting books than gardens and library nooks where someone else's\njob is to serve you tea. I also found that I was appreciating thanks\nto such a rapid infodump how much Mr. Forsberg's hesitancy was perhaps\nmore an over-eager hope of and concern for being a good host than a\nworry what sort of person he was giving access to his family's\nlibrary. I realized I had something of a term of my own as I thought\nabout Xavier's offer, &quot;I really appreciate the offer, Xavier. If I may\nadd a term of my own? If the book is what I've heard it to be, it\nbelongs in the public domain. I'd love to scan your copy to make it\naccessible as an ebook for others that may not receive such a generous\noffer from a wealthy patron.&quot;</p>\n<p>There was a very complex wash of emotions across Mr. Forsberg's face,\ntoo fast for me to catch any particular individual ones, but he\nsettled into a response I did not expect from someone of his presumed\nwealth, &quot;Ms. Lynne, it would pain me greatly to let you volunteer such\nlabor when I can easily pay for it. I've brought in Ms. Hayward as a\nconsultant on several of my past library science needs and she's been\nnothing but professional in projects such as that. But first, I would\ncertainly appreciate your opinion on that large <em>if</em> that you brought\nup. It is a remarkable book in my opinion, but I shall await your own\nbook report to compare notes, Ms. Lynne. We can discuss things like\nscanning that book after you've read it and given it time to digest,\nyes?&quot;</p>\n<p>Here again I found such an interesting and effectively charming\neagerness in Xavier's eyes especially. I nodded in agreement, it made\nenough sense, and reached out my hand. He gently dropped the key into\nmy own hand and clasped my hand in something that wasn't quite a\nhandshake and wasn't quite a gentle pet, before just as gently\nreleasing my hand.</p>\n<p>&quot;Wonderful, Ms. Lynne. I am eager to hear your book report. I've found\nthat first impressions of the family library are most interesting\nwithout a formal tour. Perhaps when you are ready to deliver your book\nreport I'll explain a bit more and show you some of the library's more\nhidden features. I can have Mrs. Montgomery show you to the library\nbuilding so you can get started.&quot;</p>\n<p>I smiled, &quot;I believe it's the hexagonal building I spotted from the\nstreet with the engravings of Athena watching its door?&quot;</p>\n<p>&quot;Yes, of course! Very astute,&quot; and with that great smile of an\nacknowledgement from Xavier, which I found strangely warming, I stood\nand thanked Mr. Forsberg again for the opportunity to make use of his\npersonal library.</p>\n<p>Standing just outside the library door I had a sense that the Forsberg\nFamily Library was probably bigger than my entire regular branch of\nthe free public library. (Though I liked that it was one of the\nsmaller branches.) The exterior door of the library lead into a very\nsimple vestibule. A couple of antique looking brass lamps lit up as I\nopened the door and I presumed they were on a modern motion sensor.\nDespite being a couple of relatively small lamps with modern LED bulbs\n(I presumed) they did a great job lighting the entire vestibule. On\none side was a tiny cloak closet and the other an old fashioned card\ncatalog. Relatedly, a QR code had been printed and posted above the\ncard catalog. A note beside it that I recognized in Ms. Hayward's\nhandwriting stated simply &quot;Use QR code for other catalog services, but\nplease continue to use the card catalog as the main index. -V&quot;. I of\ncourse knew the author I was looking for so I only needed the main\nindex, presuming it was sorted by author name. I was glad for having\nhad an eccentric elementary school teacher of mine force us all to\nlearn the basics of a card catalog, despite it being an incredibly\nuseless skill these days when all library catalogs were digital.</p>\n<p>I opened the drawer that promised to contain Milburn, J. Y. and was\nimmediately drawn into how much of a colorful explosion the cards\nwere, and I wound up taking my time flipping through its contents in\nsearch of the card I needed. Every card had the expected author name\nand book title on the left hand side, and various other details such\nas descriptions or author blurbs printed below those. Every card also\nhad at least a square of color in the upper right, like a test swatch\nfrom a painting company. In a few cases the entire cards were painted\nor printed the color of the upper right swatch. In some fun cases I\nstumbled across the swatch expanded out into mini-paintings and\nembellishments, such as on the card for Melville's Moby Dick where the\nblue swatch color was part of a blue whale trying to swallow the book\ndescription. Below the color swatches were decimal numbers, but they\ncertainly didn't seem to align with Dewey Decimal numbers in the\nslightest. For one thing, even fiction works such as that Moby Dick\ncard I passed across had these decimal numbers, which Dewey himself\nnever bothered with, and for another the few Dewey Decimal categories\nI could recall off hand when I saw similar numbers flip past did not\nmatch the expected contents at all. The card for Milburn, J. Y &quot;Flight\nInto Night— A Slumbering Fantasia&quot; was in the catalog right where I\nexpected it to be. It had a very interesting crimson/purple shade as\nits color swatch, perhaps resembling the tones of the later parts of\ndusk. A part of me wanted to keep shuffling through the cards to see\nwhat other details might pop out at me, but I was also very curious\nnow to see the library itself.</p>\n<p>With the book's card in hand I swung the door open to the library\nproper. The vestibule door opened up into a massive flood of natural\nlight from the dome overhead. The vestibule felt claustrophobic in\ncomparison to the wall of light that spilled out from the building's\ninterior. That moment of transition took my breath away even before I\nstarted to take in the contents of the library itself. The vestibule\nhadn’t seemed that dark while exploring the card catalog, but it\nfelt like it took a while for my eyes to adjust to the much brighter\nbuilding interior. Even before the shift to lending mostly ebooks,\nI’m not sure if my regular library branch ever had nearly as many\nshelves as I could see in this private library. The hexagonal shape of\nthe exterior patterned the interior as well. All six interior walls\nwere lined with shelves of course, including the wall behind me with\nthe library’s entrance door buried in the center of shelves. Looking\ntowards the interior were concentric hexagonal rings of even more\nshelves, slowly growing in height as they progressed toward the center\nand an impressively looming central hexagonal spire of shelves. From\nthe entrance angle it felt like the shelves almost made stairs for a\ngiant to ascend to a throne just below the dome.</p>\n<p>The shelves weren’t all entirely filled, presumably giving things\nroom to grow, but it was still impressive just how many books I could\nsee even before I started to explore the rings in more detail. The\nmost prominent feature of the shelves, though, was that the covers of\nthe books that shared each shelf all shared similar colors to each\nother, with subtle gradient shifts across the shelves. Each triangular\nsextant of the library was dominated by a single primary color, and\nthere seemed to be a pattern to the flow of the colors between and\namong the rings. It was a beautiful color wheel that seemed to be the\ncentral pattern to the library, though it didn’t seem to exactly\nmatch any color wheel I was familiar with.</p>\n<p>I could easily see why the swatch colors dominated the library’s\ncards so much. Assuming you weren’t color blind I could tell that\nyou could track down individual books just by color sample alone as\nyou got used to how the colors shift across the color hexagons. It was\nalso quickly clear to me based on how the shelves were marked that the\ndecimal numbers were still very handy for finding books. The hundreds\nplace seemed to exactly match the sextants numbered 1 through 6. The\ntens and ones places gave a sense of which ring, though it wasn’t an\nexact or clear cut match. I had no idea what the decimals might\nrepresent, but there were sometimes six or seven digits. Between the\ndecimal number and the color swatch it didn't take me hardly any time\nto find the book I was looking for. It was between a treatise on art\ndeco architecture and a biography of a Russian playwright, and I\ncertainly had no more idea what the decimal numbers actually\nrepresented in this library as there was clearly no categorical\norganization here.</p>\n<p>Other than the title and author on the spine of the cover I didn't see\nany other marks such as a publisher's mark. As I pulled the book off\nthe shelf it became clearer that the dust jacket wrapped around the\nbook was a unique and custom laminated work of art. It had a\nstartlingly lovely dusk painted across it from back cover to front\ncover. The setting sun was featured prominently on the back cover and\na kid whooping and hollering while riding a flying carpet took center\nstage on the front cover. No other words or adornments on either back\nor front covers except an artist's signature, &quot;R. Forsberg, Jr&quot;.\nInside the dust jacket, it was a classic dull brown leather bound book\nof the expected sort for its age. I found I really appreciated the\nbeauty of the custom family cover, helping it to feel at home on such\ncolorful shelves. A manilla card holder was the only other addition to\nthe book, and one you would expect of a library. I slid the books card\ninto its holder, just as expected and wandered over to a nearby study\narea to start into the book.</p>\n<p>It took me several weeks of trips to the Forsberg Family Library to\nfinish Milburn's &quot;Flight Into Night&quot;. The prose was magical but\nincredibly dense and slow to read. Over those weeks I became very\nfamiliar with some of the reading spots in the library itself and in\nthe garden. I did manage to find the hiking path to the city skyline\noverlook Xavier had mentioned. I had Mrs. Montgomery show me a lovely\nsitting room in one of the guest buildings when I was craving an\nindoor reading spot that was a bit more variety (and a little bit more\ncozy and comfortable, like reading on the couch of a beloved great\naunt in her country cottage). I rather took for advantage the staff's\noffer of tea service and was amazed at the selection and the variety\nof charcuterie and tapas bites that would accompany the pots of tea at\nchef's whim. I tried not to take advantage of the meal planning for\nsomewhat unreasonable fear of never leaving Mr. Forsberg's estate\nagain if I ate too much of its food. I was afraid of being\naccidentally trapped as its prisoner (of exceedingly fine dining, as I\ndid find out the one weekend I requested it). Across those weeks I saw\nXavier himself only quite rarely and mostly in passing as he was off\nto whatever kept him busy on weekends and I was sometimes directly\nrushing off to finish the next chapter.</p>\n<p>Milburn's &quot;Flight Into Night&quot; itself was equal parts amazing and\ninfuriating.</p>\n<p>The book contained some amazingly imaginative descriptions of a dreamy\nwonderland &quot;just beyond the clouds, up the stairs, through the crystal\ntunnel, then across the dusk ocean, but only when you are asleep&quot;\nnamed &quot;The Far Kingdom&quot;. The protagonist was a tween boy with a love\nof adventure and maybe just a bit of narcolepsy, and of course the\nadults in his life didn't believe such a wonderland existed, much less\nwere they interested in helping the poor protagonist in sorting out\nthe various and sundry political conspiracies threatening to blow up\nThe Far Kingdom and maybe even the &quot;bland lands&quot; &quot;below&quot; such as the\nprotagonist's home and all those adults that didn't trust him and\nconsidered him a sad little boy with a mental illness. At the end\nabout as you would expect the kid solves the political conundrum in a\nstrange but fun way, wins the interest of the Princess of The Far\nKingdom, and maybe manages to wink-nod earn some respect from the\nadults in his real life with a trinket he brings back as the Hero of\nthe Far Kingdom. Like some of its other near contemporaries such as\n<em>Peter Pan</em> or <em>Wizard of Oz</em> or <em>Little Nemo in Slumberland</em> it was\npretty tightly focused on the protagonist's view point and treated the\nadventures as very real and quite serious. Like Oz the world seems\nsetup to be a possible reusable franchise, way back before that was\nanywhere near a common thing, though the card catalogs in both the\npublic library and in the Forsberg Library seem to imply that any\nproposed sequel was never written. The obvious reason to assume no\nsequel exists is purely economic: the book sold only reasonably well\nregionally and the publisher went bankrupt soon after, but I was also\nstarting to see how some of the more problematic elements of the book\nmay have also prevented J. Y. Milburn from attempting any longer term\nplans with the material.</p>\n<p>It was in its problems that the book was incredibly frustrating. For\nall of its wonder and twee imaginative descriptions it also contained\ndeep wells of intentional and accidental racism. The politest\nsentiment you might often hear here is that the book was &quot;extremely of\nits time&quot;, but the view of the book from this particular modern\nvantage point seemed to say that maybe it was even worse than its\ntime. The use of a flying carpet as the primary transport to, from,\nand around The Far Kingdom easily implied that Milburn had studied\nArabian Nights and possibly a wealth of other multi-cultural artifacts\nand just about every time you started to feel like perhaps the\nappropriation was unintentional Milburn introduces yet another racist\ncaricature or veers rather closely to outright islamophobia or\nsomething worse. Most uncharitably the book is a white savior myth of\na boy colonizing his dream space and winning the &quot;exotic&quot; princess as\na prize for his efforts. Both things are true at once: it's an\nimaginatively charming boy's adventure and it's a mess of bad\nstereotypes and outdated views and things that are very clearly awful\ntropes from a modern lens. Can you forgive the awful parts for the\ncharm of the good parts? Can you blame a book for perpetuating bad\nstereotypes and tropes when historically it may have predated or even\nbeen the cultural source of some of them? I didn't have good answers.\nI enjoyed reading the book for the most part, and I enjoyed reading it\nin relatively disconnected from the real world context of the Forsberg\nFamily Library (and its garden and hiking paths and guest sitting\nrooms).</p>\n<p>When I finished the book I asked Mrs. Montgomery to please schedule\nsome time to chat about it with Mr. Forsberg. Xavier insisted on lunch\ntogether and I found myself unable to turn down an invitation for\nanother amazing meal from his chefs.</p>\n<p>Over lunch we mostly discussed pleasantries. It was charming and\nlovely and the food was as spectacular as I figured it would be. Over\na slice of cake and coffee the conversation finally turned to Milburn\nand &quot;Flight Into Night&quot;. Mr. Forsberg asked for my report and I found\nto my surprise I had a lot to say about it. Enough that the\nconversation flowed back out into the garden and into some afternoon\ntea. I enjoyed discussing all of the good parts of the book that\ncaptured my imagination. I got somewhat heated discussing the book's\nproblems and how upsetting some of it had been to read in this decade.\nI mentioned my gut instinct that Mr. Milburn was probably even worse\nthan average of his own time period and it wasn't entirely &quot;it was a\nproduct of its time&quot;, the theory that some of its mean spiritedness\nand racism was so well read as to be intentional in its appropriation.</p>\n<p>As conversation had subsided and with it again the tea and the various\nfruits that had accompanied it today (despite how much we ate for\nlunch, the bits of fruit felt cleansing and powerful when accompanied\nwith a delightfully floral green tea), Xavier finally asked the\nquestion I had been dreading, &quot;I'm glad that you enjoyed the book, and\nI've appreciated your perspective on its many problems. Given what you\nknow now of this book, would you still wish to recommend that I pay\nsomeone like Ms. Hayward to scan and OCR the book for consumption by\nthe internet as an ebook?&quot;</p>\n<p>I had been asking myself the same thing for days. I had been at war\nbetween the parts of me that were a big proponent of the public domain\nand keeping access to interesting cultural artifacts (especially in an\nage when so much of what should be the public domain was gated by\nleaseholders demanding rent and we could use all the public domain we\ncould get), and yet the parts of me that admired it as an interesting\ncultural artifact that is so terribly flawed it probably should never\nbe available in the context of the internet. &quot;No, probably not. I mean\nit–&quot; was about all I got out before my brain's own infighting\nstopped me short. After hours of talking about the book, talking about\nwhat to do with its future choked me up a bit.</p>\n<p>I wasn't sure how much of that Xavier caught nor how much he felt\nhimself, but he stood, unfolding to forbidding height in the process\nas usual, and offered a change of topic, &quot;I offered you a personal\ntour of some of the secrets my family's library in exchange for your\nbook report, would you like to join me?&quot; He offered his hand to me,\nand I accepted it.</p>\n<p>&quot;My father considered himself a failed painter,&quot; Xavier started as he\nlead the way in a direction I felt pretty familiar with, &quot;He went to\nart school against his father's wishes, was never commercially\nsuccessful, and eventually returned home. I'd submit that my father's\nview of success was myopic, but I'm biased, in part because our\nlibrary was my father's greatest painting. Like many of the greatest\nworks of art it was never completed in my father's lifetime, but like\nonly a few truly great works of art this was because it was designed\nto be a never ending, living installation.&quot;</p>\n<p>We started with a lap around the exterior of the building. Mr.\nForsberg was full of facts about little details and secrets his father\nhad hidden in the architecture, of which his father had first draft,\nbut it sounded like he'd contributed his own share of details. I have\nno real knowledge of architecture with which to appreciate most of\nsuch secrets, but Xavier kindly pointed out that some of the books in\nthe library could provide additional context if I wanted it. (Though\nI'd probably need to look them up via Ms. Hayword's digital secondary\nindexes.)</p>\n<p>Stepping into the vestibule, after a few fun facts about the brass\nlanterns and seeking out a custom LED manufacture for their unique\nsockets, Xavier pointed out what I had come to assume about the card\ncatalog, &quot;My father of course knew about Mr. Dewey's system and he\nhated it. He thought that it removed the creativity of unusual\njuxtapositions, as you can maybe now guess. He grew up with my\ngrandfather's random shuffled library, with books in shelves spread\nhaphazardly throughout the house, and as much as he loved the\nunorganized mess he wanted to organize it somehow. He considered Mr.\nDewey's system boring and problematic for the ideas in books to only\nbe in conversations with each others in the same 'intellectual\ncategory'. Most importantly he thought it robbed books of their\ncontext: pigeon holing books into strict categories implies that\neverything about those categories can be exclusively found in those\nand only those books, that every book in a category is equally\nvaluable to current thought on that category, and that perhaps worst\nof all the impression of full shelves in a category can give the\nimpression that everything has already been learned and written down\nin that category. My father wasn't trained in the library sciences, he\nwas a failed painter, so he used the tools that he knew: colors. Then\nhe arranged them into numbers that made sense to himself and thought\nto arrange books by the most interesting color on their spines.&quot;</p>\n<p>I nodded along at that, and even knew what to fill in of the next\ndetail thinking of the delightful dusk colors of the cover on\nMilburn's &quot;Flight into Night&quot; and the &quot;R. Forsberg, Jr.&quot; signature on\nit, so I added, &quot;And where books didn't have interesting colored\ncovers, he painted some beautiful ones himself.&quot;</p>\n<p>Xavier clapped in delight at my observation, &quot;Indeed. Books were\nalways my father's greatest muse and organizing the family library\ngave him decades of reasons to paint beautiful covers for old books\nthat needed new or different contexts.&quot;</p>\n<p>He opened the door into the main hall of the library and ushered me in\nbefore continuing, &quot;Of course, a lot of them needed new contexts,\nincluding Mr. Milburn's <em>Flight Into Night</em>. My grandfather and my\ngreat grandfather were quite the collectors in their respective times,\nand as you may suspect of nearly any family of multi-generational\nwealth in America such as mine we had more than our fair share of that\nmade on the backs of other people and some of that was intentionally\nmore than a little racist. My father didn't believe in hiding the\nskeletons in the closet, but bringing new contexts and bright shining\nlight to the horrors, as a reminder and a caution for future\ngenerations.&quot;</p>\n<p>Xavier ran a hand over a seeming random shelf and just about\nimmediately spotted an example, sliding it off the shelf. It had\nfascinating bright cover, but intentionally no words on the spine. The\ndust jacket cover was another &quot;R. Forsberg, Jr.&quot; original, only this\ntime a gathering of ghosts with shocked and angry faces in horror. Mr.\nForsberg slipped the laminated dust jacket off just enough to reveal\nan old leather bound volume with a long disgustingly clinical title\ninvolving &quot;trepanning&quot; and that was more than enough than I needed to\nknow for why those ghosts were horrified, angry, and shocked. Xavier\nshivered in a shared horror of his own, replaced the dust jacket, and\nthen reshelved the book. He pointed to its neighbors, &quot;Today such a\nhorror has as its primary chatting companions a field guide on birding\nand a novel about teenage self-esteem. Perhaps it will learn from such\nbetter neighbors. I've had Ms. Hayward project my father's\nidiosyncratic system to Mr. Dewey's and even modern color numbers such\nas HSL and CIELab, but the idiosyncratic system is what makes this\nlibrary the strange incomplete work of art that it is.&quot;</p>\n<p>I felt like I was still reeling at the given &quot;random&quot; example, &quot;Why\nwouldn't you burn a book that awful rather than shelve it?&quot;</p>\n<p>Mr. Forsberg's eyebrow quirked up, &quot;Weren't you the one trying to\nconvince me of the value of old books to the public domain as a term\nof accessing this library?&quot;</p>\n<p>I sharply inhaled as if hit. He was obviously right, and I suppose\nthat now I had a lot more context to work on, including the racism I\nhad directly combated in my reading of Milburn's &quot;Flight Into Night&quot;.</p>\n<p>Xavier smiled and there was a kindness I didn't expect or know what to\ndo with in his muppet of a monster's face after such a surprising\ninsult, &quot;Come, please. Allow me to show you my favorite secret in the\nlibrary.&quot; It dawned on me that this wasn't the first time that he had\nhad a conversation such as this, and that as embarrassed and pained as\nI felt from his response, I had a lot more reason to understand that\ncomplex wash of emotions when I had so casually discussed scanning\nbooks for posterity. This family's library was for a different sort of\nposterity, as it collected as much of the bad as the good.</p>\n<p>He lead me to the opposite wall in the hexagon from the vestibule\ndoor. He ran a hand along the shelves looking for a particular volume\nand it shouldn't have surprised me when pulling it off the shelf\nrevealed a hidden button behind it. Pressing the button made the sound\nof a great mechanical &quot;kerchunk&quot; of presumably a locking mechanism and\nthen just as smoothly as Xavier replaced the book on its shelf an\nentire section of shelf next to that book slid out on rollers as a\nhidden door. Behind the door was a simple flight of stairs running up\nfrom the main hall of the library. I spent so much time in the library\nand I would not have suspected a hidden door or that the hidden door\nmight have stairs leading up of all places. I eagerly followed Mr.\nForsberg up the steps to see where they lead.</p>\n<p>&quot;Oh wow,&quot; I stated overwhelmed and feeling like such words were not\nnear enough as I stepped out from the stairway into a gallery tucked\nunder the dome. Below the gallery you could see the entire color wheel\n(color hex) of the library laid out before you as a beautiful rainbow\nroom. I wasn't sure what sort of magic angles had been applied to the\nshelves below to deliver the &quot;trump l'oiel&quot; special effect below that\nmade it feel to me like I could see all the colors of the covers of\nthe books from no matter what angle I was looking down from as I\ngently wandered the circular gallery.</p>\n<p>Xavier chuckled at the way I was straining my head around trying to\nsee if I could catch the trick of it, and he gave me some time before\nfinally filling the space with the rest of the conversation as if it\nhadn't been interrupted by beauty below our feet, &quot;This is the big\nreason I felt I must insist the books don't leave the family's\ngrounds, this final overhead context. Just as culture itself tries\npiece by piece to make the best things it can out of the best parts of\nthings that came from before it by recontextualizing them, this\nlibrary is one grand work of recontextualization. Mr. Milburn's book\ninspired a jazz quartet to create a lovely dream suite that in turn\nwas sampled by some of the hip hop classics. Mr. Milburn's book inspired a\nprogressive children's television show that tried to equally celebrate\nchildren's imaginations while also encouraging diversity as the way to\ndo it, rather than an obstacle in the way as Mr. Milburn seemed to\nbelieve. Mr. Milburn's work is better for how culture already moved on\nand readapted the good parts, leaving bad ones behind. So too, Mr.\nMilburn's work is better as one part of a prism's rainbow in a library\nwhere new contexts matter more than old ones.</p>\n<p>&quot;My father insisted on the big natural light dome and that the best\nprisms came from sunlight. The best way to fight darkness in our\nhearts, in our culture's past, in our family's wealth, was to shine\nnot just any light on it, but sunlight. My father knew full well that\nthe sun would over time paint its own brushstrokes of fade patterns\nacross the colors in the canvas below and that sort of collaboration\nwas something my father always trusted, and that too felt symbolic to\nmy father.&quot; I noticed the streaks of tears on Xavier's face, and felt\nmore than a little moved myself. There didn't feel like much to say in\nresponse while I digested that. I continued to take in the sight of\nthe library below me and we took our time before we left the gallery\nfor supper.</p>\n","date_published":"2023-11-16T00:00:00.000Z"},{"id":"https://blog.worldmaker.net/2023/09/05/pharkas-complete/","url":"https://blog.worldmaker.net/2023/09/05/pharkas-complete/","title":"One Last Feature for Pharkas Component Framework (for Angular)","content_html":"<p>With version 6.0.0 I released one\n<a href=\"https://blog.worldmaker.net/2023/09/05/pharkas-complete/#the-state-of-pharkas-in-late-2023\">last</a> feature for the\n<a href=\"https://worldmaker.net/angular-pharkas/\">Pharkas Component Framework</a> and with version 7.0.0 I\nupdated it to the Angular's current LTS (15). Pharkas is a mature,\nbattle-tested library for writing user-responsive UI components\nusing RxJS best practices in Angular. These two updates are likely\nmy bow around my involvement in the project.</p>\n<h1>A Brief Intro to Pharkas</h1>\n<p>Pharkas came from <a href=\"https://blog.worldmaker.net/2021/06/26/angular/\">my frustrations with Angular</a>, especially\nwith how many core components of Angular may unintentionally lead to\nbreaking RxJS best practices. At that point I had a nascent idea of\na framework for better RxJS practices in Angular, which I briefly\nnicknamed <a href=\"https://blog.worldmaker.net/2021/06/26/angular/#suggestions-for-a-better-angular-project-gawky\">&quot;Project Gawky&quot;</a>.</p>\n<p>A few months later <a href=\"https://blog.worldmaker.net/2022/10/30/angular-components/\">while debugging and fighting a Halloween sort of\nghost story in Angular</a> caused by Angular's default change\ndetection system and its (over-)reliance on Zone.js, I felt pushed to\nreevaluate the &quot;Project Gawky&quot; ideas in a concrete base library that\ncould be used in brownfield applications as a way to make it much\neasier to adopt Angular's more civilized <code>OnPush</code> change detection.\n(At least from a &quot;bottom up&quot; direction, as default components can\nuse <code>OnPush</code> ones easily but vice versa isn't as possible or useful\nin Angular.)</p>\n<p>Pharkas defines something of a DSL for component building in Angular\nthat is entirely dependent on RxJS Observables, entirely focused on\nworking solely with them to drive a component's behaviors (including\nlife cycle), and built to make it easy to follow RxJS best practices.\nThese include helping developers to avoid over-relying on Subjects,\nand minimizing calls to Observable <code>subscribe</code>, and especially\nattempting to eliminate calls to <code>subscribe</code> without a corresponding\n<code>unsubscribe</code> cleanup at the right component life cycle point. The\nbenefits to following these best practices (as opposed to many\n&quot;best practices&quot; Angular developers often are exposed to in the wild)\nshould always be a reduction of memory leaks.</p>\n<p>Pharkas takes this further by automating the somewhat manual <code>OnPush</code>\nchange detection in Angular. Change detection is handled entirely\nbased on Observable wiring. Pharkas is designed to lead the component\ndeveloper into <em>user-responsive</em> components by default.</p>\n<p>Pharkas' &quot;DSL-like&quot; patterns have been occused of being a bit\nverbose (an unfortunate side effect of Angular's use of non-standard\ndecorators), but in practice I've often found component code to be\nsmaller and easier to read when written with Pharkas. Once you\nfactor in the easy wins from Pharkas' smarter out of the box change\ndetection behaviors, full mostly automatic component life cycle\nmanagement, and other subtle performance benefits, I think the\n&quot;verbosity&quot; easily seems to disappear and Pharkas for became the\nonly good way to write Angular components.</p>\n<p>I think Pharkas makes the best way to write components when\nperformance matters. I think Pharkas also excels at writing thinner\nwrappers around Vanilla JS components avoiding unnecessary Zone.js\noverhead taxes across the component boundaries. (Vanilla JS\ncomponents that handle all their own DOM don't need Zone wrappers\nand with Pharkas wrappers will never signal extra busy work to\nAngular's change detector.)</p>\n<h1>Basic Suspense (The Last Pharkas Feature)</h1>\n<p>In reviewing the <a href=\"https://blog.worldmaker.net/2021/06/26/angular/#suggestions-for-a-better-angular-project-gawky\">&quot;Project Gawky&quot;</a> I think the final Pharkas\nproduct managed to in general meet most of the expectation (outside\nof being directly a template language dialect rather than a DSL\ninside component constructors). These ideas came largely from having\nwatched a lot of what React had been doing, very slowly, across\nmultiple major releases. I felt like a lot of those same benefits\ncould come from an RxJS-first approach, as much of it looked like\nbuilt-in schedulers and common operator patterns.</p>\n<p>From the beginning Pharkas has made easy in Angular to do a lot of\nthe complex timing management that React calls &quot;Concurrency&quot;:\nany Observables bound to a template with Pharkas\n<code>this.bind(name, observable, default)</code> will notify Angular's\nchange detector no faster than <code>requestAnimationFrame</code> time. This\nkeeps components highly user-responsive, as the browser will\nthrottle <code>requestAnimationFrame</code> time as necessary to focus on\nuser interactions and keep the UI responsive. (A browser may also\nreduce <code>requestAnimationFrame</code> time when a tab is hidden or\nbackgrounded, further improving battery efficiency.)</p>\n<p>(Of course, user-responsive also means that sometimes you need\nchanges propagated as soon as possible, and Pharkas also has\nfrom nearly the beginning provided\n<code>this.bindImmediate(name, observable, default)</code> to force immediate\nchange detection on observations. In React this sort of thing is\nneeded especially for form elements when the &quot;source of truth&quot; is\nthe virtual DOM; form updates need real time to avoid upsetting\nusers. In Angular, Reactive Forms treat the real DOM as source of\ntruth and I've found the need for immediate bindings in Pharkas\nextremely rare.)</p>\n<p>As I had surmised, this is easily accomplished by a relatively\nsimple RxJS operator and scheduler combo (it is about entirely\njust <code>debounce(0, requestAnimationFrameScheduler)</code>) when you, like\nPharkas, are assuming entirely Observables (and nothing but\nObservables), and are using Angular's <code>OnPush</code> change detection\nstrategy.</p>\n<p>The other side of the React example over multiple major versions\nwas what React calls &quot;Suspense&quot;. I had mentioned that among the\n&quot;Project Gawky&quot; ideas, but hadn't implemented until now. In Version\n6.0.0 Pharkas learned a very basic version of &quot;Suspense&quot;: a component\nmay <code>this.bindSuspense(suspenseObservable)</code> to determine when to\nraise a flag that the component is suspended. While suspended,\nPharkas will suspend all further change notifications to the Angular\nchange detector until the flag is lowered.</p>\n<p>One motivation for a suspense observable is loading situations where\nyou want to display a simple loading UI and fewer of the intermediate\ntemplate states while it is loading. Another possible motivation\nwould be situations where you may expect a lot of expensive\ncalculations and want to avoid browser DOM work while it happens.</p>\n<p>It's another (final) tool in the user-responsive toolbox. Immediate\nbindings still trigger change detection, and Angular will do\n(opaquely) choose to do change detection on its own every so often.\nIt won't entirely eliminate &quot;intermediate&quot; states in your UI, but it\nwill certainly provide a knob to tune refresh rates in periods of\napplication time where there are other priorities than DOM updates\nof your component.</p>\n<p>It's not quite apples-to-apples with the full power and complexity\nof React's Suspense, but it's an interesting, basic relative that\ncan deliver similar experiences in similar use cases.</p>\n<p>It's also nearly as &quot;simple&quot; from an RxJS standpoint under the\nAngular <code>OnPush</code> regimen in Pharkas: effectively just an extra\n<code>combineLatest</code> and <code>filter</code> added into the change notification\npipeline. (It's also &quot;pay to play&quot; and if no suspense observable is\nbound, these additional pipeline steps aren't added.)</p>\n<p>It's not very discoverable, but you can see it in action in the\n<a href=\"https://worldmaker.net/angular-pharkas/demo/index.html\">Pharkas demo</a>. If you click the test component with a counter\nthe demo will now toggle suspense for that component. You may use the\nembedded <a href=\"https://github.com/cartant/rxjs-spy\">RxJS-spy</a> in your Dev Tools console to verify that the\ntimer observable updating the counter continues to fire on its usual\nschedule and that the demo isn't cheating in this &quot;loading&quot; suspended\nstate.</p>\n<h1>The State of Pharkas in Late 2023</h1>\n<p>Pharkas is mature, stable, and now feature complete with respect to\nmy original vision for it. It is up to date with respect to current\nAngular LTS (15).\n<a href=\"https://github.com/WorldMaker/angular-pharkas\">Pharkas is MIT licensed open source on GitHub</a>.</p>\n<p>I suspect this Suspense feature will be the last feature for Pharkas,\nfor several reasons: Because it feels feature complete and because\nI have no current expectations to continue working with Angular.</p>\n<p>Feature-wise, I think the one thing left that bugs me a small bit\nstill is that I would love to make the DSL prettier with even more\nmeta-programming should Angular ever finally stop using non-standard\ndecorators so much. Maintenance expectations for any Angular library\nare of course constant updates to keep up with LTS. That will never\nlikely be &quot;complete&quot; given the way Angular compatibility tends to\nwork and the complicated nature of Angular's peer dependencies in\nnpm. I expect maintenance to be needed, but I don't expect to do it\nat this point.</p>\n<p>In large part this is mostly because I was recently let go from my\njob of the past 8 years and am looking for new opportunities and\nchallenges. I don't have any Angular apps to maintain now and I\ndon't know if I can entirely avoid Angular in whatever my next\nopportunity is, but I know I'm not going to be especially looking\nto continue working with it. (It almost drove me crazy.) Without\nsomeone paying me to maintain Angular apps, my interest in keeping\nup with the Angular maintenance treadmill falls off a cliff.</p>\n<p>I think Pharkas is mature and stable. I believe in open source, and\nwill entertain suggestions for new maintainers of the project. In the\nmeantime, I will try my best to welcome pull requests and run\nmaintenance tasks if politely asked.</p>\n","date_published":"2023-09-05T00:00:00.000Z"},{"id":"https://blog.worldmaker.net/2023/04/29/picard/","url":"https://blog.worldmaker.net/2023/04/29/picard/","title":"Playing with the Bones of Star Trek: Picard","content_html":"<p>The other day on Mastodon I got to thinking and talking again about how some of the\nideas of <em>Star Trek: Picard</em>'s first season really with resonated me, but that it was\nmostly a problem of execution of them to me, and that was part of why I watched the\nfull of its second season despite disliking so much of it. (I haven't finished watching\nthe third season, and am not sure if I will, but I did read all the spoilers for it.)</p>\n<p>I'm going to lead with this: The key motivation for me was how important I think the\nBorg <em>Coöperative</em> are to Star Trek and why I loved the <em>idea</em> of late in life (ex-)\nAdmiral Picard being the only diplomat who could &quot;properly&quot; welcome them to the\nFederation. A bunch of the seeds were laid in Season 1 for the Coöperative, and then\nSeason 2 entirely ignored them to do its own weird thing, in the process messing up some\nkey parts of the Coöperative (not just in a &quot;that's not <em>my</em> canon&quot; nitpicky way but\nmissing some of the underlying raison d'etre and character motivations, including\nunthethering it from the &quot;Prime&quot; timeline altogether in a &quot;we don't even trust it to <em>be</em>\ncanon&quot; sort of way).</p>\n<p>It feels important to lead with motivation like that, because that's going to be a\nrunning theme in what follows.</p>\n<p>(Also, I'm using the rare-to-English diaresis for the word &quot;coöperative&quot; in this\narticle mostly because it is fun and I can pretend to be high society like <em>The New Yorker</em>,\nbut also partly because it is fun how it can be confused to be Swedish enough to be a\n&quot;Borg umlaut&quot;.)</p>\n<h1>A Brief History of the Borg Coöperative</h1>\n<p>A quick informational aside: Star Trek canon is primarily broken down into three main\nwiki databases. Memory Alpha tracks TV and movies, Memory Beta tracks video games and\nnovels, and Memory Gamma tracks everything else from comics to toys to all sorts of\nweird tie-ins with a vague notion of continuity. So terms like &quot;Alpha canon&quot; are a useful\nshorthand for &quot;Star Trek stuff that happened on the TV screen or a movie screen&quot;.</p>\n<p>In Alpha canon prior to <em>Star Trek: Picard</em> there is one single, mostly standalone\n<em>Star Trek: Voyager</em> episode teasing the idea of the Borg Coöperative. The basic concept\nis that which is implied by the name: a group of Borg, disconnected from the <em>Collective</em>\n(we all know and &quot;love&quot;), decide to try to build their own culture with something more\nin lines with Federation ideals and as a &quot;worker-led coöperative&quot; where they could elect\ntheir own leaders and strive for a better Borg society.</p>\n<p>In the classic fashion of Voyager, the idea is never revisited, there's no ongoing plot\nthreads from that episode, there's no idea what happened to those people and that\n&quot;movement&quot;, it was just one mostly self-contained episode among many.</p>\n<p>So, of course, being a good idea with interesting repercussions, Beta canon had a blast\nwith it for a couple of decades. I mostly am aware of how <em>Star Trek Online</em> portrays\nthe Coöperative, but I read several of the key novels as well, for various reasons. The\nbasic throughline I think is pretty obvious: in the Delta Quadrant power vacuum left by\nthe death of the Borg Queen and infectious destruction of a lot of key Borg communication\nchannels, the nascent Coöperative of that lone Voyager is given a massive petri dish of\nformer Borg Collective toys with which to grow and peacefully expand their culture. Over\ntime, depending on source/take, even notable figures such as Hugh and Seven of Nine serve in\nvarious <em>elected</em> positions in the Coöperative (Presidents and Councilors and such),\nfurther enriching the knowledge transfer of Federation ideals, until eventually the\nFederation recognizes the Coöperative as proper <em>allies</em> (including in the fight against\nstill deadly remnants of the Collective) and then eventually <em>members</em>.</p>\n<p>The Coöperative is such a fun idea to Beta canon because the irony is never lost on most\nof the Beta writers that the Collective is frightening for how <em>quickly</em> they assimilate\nother species, but the Collective is <em>not</em> the greatest assimilation force known to the\nStar Trek galaxy: the Federation is <em>much</em> slower at assimilation, but the history of\nStar Trek suggests it is more deeply the winner. The Borg Collective gets a couple of\ninteresting battles deep into the heart of the Alpha Quadrant but mostly fails to get\na lasting beachhead, stymied time and again (and back in time) by the Federation.</p>\n<p>The Federation frees what seems like only a few dozen Borg total, infects them with\nFederation <em>ideals</em> and virtues, sends most of them along on their merry way with\nseemingly zero after-care or oversight, and <strong>most</strong> of that was done by a <em>single</em>\nship <em>lost</em> in the Delta Quadrant, the Borg Collective's home turf, that was barely\na threat (though did get time paradox lucky to eventually kill the Queen, but it\nwas either that or more years in hell, so Captain Janeway felt free to violate\nseveral sections of the Temporal Prime Directive for that outcome in the Prime\ntimeline). It wasn't an attack force. It wasn't a planned strike. It was one silly\nship <em>talking to people</em>.</p>\n<p>If you've been following along in Alpha canon, that irony of the Borg Coöperative\nbeing an <em>accident</em> of infection of Federation ideals shouldn't be a surprise. The\nAlpha canon is full of Federation &quot;exceptionalism&quot; like that. Time and again, the\nmany shows suggested that the Federation was more enlightened than everyone else and\nconcepts like democracy and Infinite Diversity in Infinite Combinations would always\nwin in the face of just about anything. There's something delicious about that\nwinning even against the Borg Collective's culture, even if some of it was an\naccident and all of it was a very slow form of assimilation.</p>\n<p>Beta canon followed that to some of its logical extremes: that picture of Borg\nCoöperative members serving in Starfleet as another &quot;species&quot; adding diversity of\nthought and culture to the Federation.</p>\n<p>I personally love that. That's my Star Trek.</p>\n<h1>The Immediate Problems with <em>Star Trek: Picard</em></h1>\n<p>I'm going to get back to the fun shortly enough, but I feel in the interest of\ncontext I briefly need to mention some of the biggest shortcomings I saw in\n<em>Star Trek: Picard</em>.</p>\n<p>I think it is clear at this point that <em>Star Trek: Picard</em> put the Borg Coöperative\nonto the tee, went to swing at it, and entirely whiffed it.</p>\n<p>It's <em>implied</em> but never actually text in any season of <em>Star Trek: Picard</em> that\nthe Borg faction that bookends Season 2 is even the Borg Coöperative. It seemed\nobvious to me, with my exposure of Beta canon, but it has been the source of a lot\nof Season 3 confusion.</p>\n<p>The other thing that bugs me the most about how Season 2 presented their origin of\nan Almost-Coöperative is that it misses some of those key bits of Federation ideals\nthat make not just the Beta canon Coöperative but also the brief, nascent\nCoöperative that Voyager briefly interacted with in a single episode. The\nAlmost-Coöperative is presented as having a traditionally symbolized Queen and\nwhile they request to join the Federation don't seem to have <em>internalized</em>\nquite so much of Federation ideals.</p>\n<p>Though admittedly a lot of that is still <em>implication</em> and <em>supposition</em> because\nthere is not enough <em>text</em> there in Season 2. Maybe Agnes there is just an\nelected Ambassador chosen based on a bunch of information, and not actually a\nQueen. We have basically no idea because they showed us the &quot;twist&quot; that it\nwas Agnes then didn't really have time to <em>tell</em> us anything about that. Twist\ndone, the show decided the only thing it really had left to do that season was\nroll the credits credits.</p>\n<p>That leads into what I think were the largest problems of <em>Star Trek: Picard</em>:\n&quot;No tell, only show&quot; and &quot;twists/late infodumps over exposition and character\nmotivation&quot;. Neither of these problems originate with nor are unique to\n<em>Star Trek: Picard</em>. One is just kind of an accidental product of post-<em>Lost</em>\nmedia. Everyone <em>knows</em> &quot;People love twists!&quot; Everyone forgets that we used\nto generally know character motivations as an audience and have some semblance\nof reasons to watch a given scene rather than assume it <em>might</em> be relevant\nlater, maybe, if the &quot;twist&quot; is right and it wasn't a red herring.</p>\n<p>The &quot;No tell, only show&quot; seems like the obvious pendulum extreme affecting\nalmost all of current &quot;prestige television&quot; and precisely what you'd get\nfrom a generation and a half of writers drilled on &quot;Show, don't tell&quot;.\n&quot;Show, don't tell&quot; comes from a good intention. It's meant to spark\ncreativity. But it's a pendulum and both extremes are painful. Too much &quot;tell&quot;\nand not enough show and the audience is bored because they are getting\ndidactically lectured at with nothing interesting to see. Less appreciated is the\nother extreme: too much &quot;show&quot; and not enough &quot;tell&quot; is a recipe for boredom\nin its own way. Things that might have taken a two minute conversation take\n20 minutes of scenes across two or three episodes. Without knowing their\ngreater context in the scheme of things those scenes can feel like complete\nwastes of time for maybe a brief &quot;oh that's what <em>that</em> was about&quot;. (Which\nis easily confused for &quot;twists&quot; and exacerbates and feeds the other problem.)</p>\n<p>To me <em>Star Trek: Picard</em> is one of the most emblematic shows in all of\ntelevision for &quot;No tell, only show&quot; especially because we have the context\nof <em>Star Trek: The Next Generation</em> (and TOS, Voyager, Enterprise): <em>so many</em>\nTNG episodes are an amazing balance of tell and show. <em>So much</em> of Star Trek\nstorytelling is dropping the right &quot;Captain's Log&quot; at a good moment to quickly\npush the audience on to the next thing they need to know.</p>\n<p>The combination of these problems leads me to this feeling like most of the\nstory of <em>Star Trek: Picard</em> was told &quot;backwards and upside down&quot;.</p>\n<h1>The Rearrangeable Bones of <em>Star Trek: Picard</em> Season 1</h1>\n<p>I have this impression that you could especially take the first season, &quot;simply&quot;\nreorder it, and tell the same story <em>better</em>. That if you focused on getting\nmore motivations up front, rather than saving things for &quot;twists&quot; that don't\nquite impact because you had no idea of character motivations. Especially, if\nwe assume that one of the end goals, because of the second season bookends,\n<em>was</em> to set up seeds for the Borg Coöperative.</p>\n<p>I have this gut feeling that most of what follows could possibly be done in an\nedit bay with a talented Editor using just the existing show as it was and\n<em>maybe</em> a choice &quot;Former Admiral's Log&quot; voice over here or there.</p>\n<p>Here's what I imagined this could play out (the other day, mostly\nextemporaneously on Mastodon; I'm sticking to mostly light edits, plus\nnew asides):</p>\n<ul>\n<li>In the deep past Romulans discover Mass Effect’s Reapers also exist in\nStar Trek</li>\n<li>This drives Romulan paranoia in general. The Romulans build a cult around\nthis and also make it a founding mission of the Tal Shiar to destroy synthetic\nlife before it starts</li>\n<li>This kind of explains why a species with the science of Vulcans but fewer\nmorals ultimately creates/enslaves the Remans rather than builds robots</li>\n</ul>\n<p>Dropping in a quick aside here, to break the bullet point rhythm, it's still\nan incredible shame that given the events of <em>Star Trek: Nemesis</em> that not\na single named Reman existed in <em>Star Trek: Picard</em>. Since I'm busy armchair\nquarterbacking my way through a rewrite of the show anyway, I'd have shown\nthe Qowat Milat to be primarily Reman and a <em>Reman Unificationist</em> group.\n&quot;The Way of Absolute Candor&quot; versus the culture of secrecy and lies of Romulus\nprior to the Hobus explosion seems almost <em>obviously</em> an outside force and\ncertainly in my headcanon easily sounds like a Reman concept. (Sure, it is\nimplied to mean more like a Romulan counter-movement/&quot;Buddhist reformation&quot;\nof Surak's teachings on Vulcan, and &quot;absolute candor&quot; in emotions primarily,\nbut you don't just drop that title in the middle of the Tal Shiar-obsessed\nRomulans and expect it to not also imply fewer secrets and lies.) Probably\nElnor should have been a Reman. That's about all I plan to say about Elnor.</p>\n<p>Back to the story reorder in progress:</p>\n<ul>\n<li>Romulan Tal Shiar infiltrate Starfleet top brass (because of course they\ndo, Tal Shiar!)</li>\n<li>Romulans blow up Federation synthetics and blame it on Synthetics going\nrampant, just like all those old Earth movies predicted</li>\n<li>Nobody catches this but the ex-Tal Shiar love interest of a retired\nAdmiral who lost all his political power trying to save Hobus explosion\n(Romulan destruction) escapees and largely failed</li>\n<li>No one believes the nutty anti-Romulan conspiracy theory from the fuddy\npro-Romulan ex-Admiral</li>\n<li>Having “gotten away with it” the Tal Shiar move on to their next priority\ntarget acquired from Starfleet intelligence: a derelict Borg cube\nreclamation process</li>\n<li>The cranky old Admiral convinces a weirdo civilian and small weird crew to\ndrive him there to that cube to desperately catch the Romulan spies in the\nact, even though no one believes him</li>\n<li>Along the way they stop at Omega Station (also imported from Mass Effect,\nbecause why not) for light hijinks and to pick up 7 of 9</li>\n<li>They don’t catch the spies in time and the Romulans damage the project near\nto destruction</li>\n<li>7 of 9 finds that she can at great risk reactivate much of the old Borg Tech,\ngets repairs kicked off (just in time)</li>\n<li>7 of 9 is <strong>so</strong> tempted to become the cube’s new Queen, but manages to get\nnew safeties installed with a bit of Federation flavor</li>\n<li>Lost for where the trail leads next the Admiral and odd crew stumble into\na scientist on the project who thinks she has a good idea where the trail\ngoes next</li>\n<li>That scientist (Soji) tells the Admiral that she had leaked details of her\nhome colony to the Tal Shiar agents, thinking them friends, but is cagey\nabout it</li>\n<li>They go there and are greeted by the kindest Soong in the history of Soongs\nand a whole colony of androids</li>\n<li>The Tal Shiar threaten to blow up the place “because the Reapers will\nreturn soon”</li>\n</ul>\n<p>In that order, even as just bullet points, I feel like that’s a pretty good plot.\nCertainly better than what Season 1 of <em>Star Trek: Picard</em> seemed to <em>show</em> us,\nright?</p>\n<p>I think that leaves clear villains the whole way through with clear motivations.\nI think that it leaves lots of building blocks for wilder things to come &quot;next\nseason&quot;. Again, I don't think I actually deviated from the story actually in\nthe show (near as we can figure in some places where things are far too much\nsubtext rather than text because the show generally abhors &quot;text&quot;).</p>\n<h1>Missing &quot;Bones&quot; in Season 1 of <em>Star Trek: Picard</em></h1>\n<p>I think that there's only a couple things that I would have loved to <em>see</em> or at\nleast have <em>told</em> in that season, rather than leave things to supposition.</p>\n<ul>\n<li>The Tal Shiar agents continue to evil monologue about the Reapers as confession\nbefore a Federation war crimes tribunal</li>\n</ul>\n<p>I like to highlight the Federation ideals here. Don't murder the villains, let\nthem face justice and account for their actions.</p>\n<ul>\n<li>Don’t show the Reapers to any main character, but only tease the audience they\nare real</li>\n</ul>\n<p>We don't know anything about this species other than a brief glimpse through what\nmight have been a portal. There's no text here, just wild special effects.</p>\n<p>Beta canon suggests it <em>could</em> be any number of things with a common fan favorite\nbeing the TNG-introduced Iconians of the Iconian Empire using a classic Iconian\nGateway, which yes all do predate Mass Effect by some time.</p>\n<p>Despite mocking this element of the show as just ripped from Mass Effect, I\nappreciated. I like my Mass Effect most when it is a lot like Star Trek, and\nI did like a lot of things about when Season 1 felt the most like &quot;Picard Effect&quot;.</p>\n<p>I love the idea that at all times in Starfleet history there are barely known\nexistential threats that are just out there, encountered once, if at all, and\n&quot;never followed up on&quot;. Part of the fun of something like the Beta canon is\nall the fan hype about &quot;This time it is the Iconians for sure!&quot; but Alpha canon\nhas never mentioned them again outside of a throwaway gag in Voyager and a\nthrowaway mention in Discovery (centuries after TNG).</p>\n<p>It's great to have these bits of unexplained whatever. &quot;Give us more tell&quot; also\ndoesn't mean &quot;tell us everything&quot;, it's still a pendulum. Briefly show and\nleave unexplained works well when <em>intentional</em>.</p>\n<ul>\n<li>Redeem Soongs and invite back to Federation, finally</li>\n</ul>\n<p><em>Star Trek: Picard</em> seems to get so close to doing this. I like the idea of\na completed redemption arc for the Soong family over way too many generations.\nThe Soongs are exiled from the Federation for getting too deep into\nFederation-outlawed genetics experiments, while in exile realize that genetics\nexperiments are tough without a big enough population to experiment upon, and\npivoted to never technically outlawed by the Federation research into robotics\nand sentient machines. (Obviously the Federation invested quite a bit into\nthese things still. The Exocomps as the easiest next thing to point to. It\nwas certainly not outlawed in the Federation to science these things.) It is\nplausible that being in exile gave them a somewhat leg up and some of their\nexperiments even in that time might not have met Federation ethics standards.\nBut TNG and then <em>Star Trek: Picard</em> make it clear that once the research was\ncomplete, the Federation had much fewer qualms about taking advantage of the\nfinal products. (After all, what <em>is</em> the measure of a man?) (At least until\nthat Romulan spy sabotage thing, whoops.)</p>\n<p>The right thing to do would be to welcome the Soongs back into the Federation\nafter their androids (and &quot;synthetics&quot; derived from same) had done so much for\nthe Federation for so many years. It's cool and all to see &quot;yet another&quot; sad\ngoodbye to Data himself, but welcoming the Soongs back into the Federation I\nthink would be a much more interesting, weird, but satisfying ending that the\nshow could have uniquely given us. It's a finale to a TNG arc (that ENT\nexpanded) a lot of people wouldn't see coming but I think would grant a strange\namount more of closure to Data as a character than any &quot;chess ending&quot; or &quot;poker\nhand&quot;.</p>\n<h1>The Borg Coöperative Bones of Season 2 of <em>Star Trek: Picard</em></h1>\n<p>I think the bones are there for something really interesting in S1 setting up\na teed shot for the Borg Coöperative. S2 mostly seemed not to know how to use them.</p>\n<p>I think some of these were good bones in S2:</p>\n<ul>\n<li>7 of 9 is given a glimpse of being “Queen” in a fascist timeline or mirror\nuniverse and further reviled by it</li>\n</ul>\n<p>The Borg Coöperative should not have a Queen. It should be more of a democracy.\nThat idea that the Collective's culture is so strong that it really wants <em>a</em>\nQueen, and that in that position Seven of Nine realizes she's incredibly\ntempted and has such a hard time resisting. (It is futile, as they say.) Using\nan alternate timeline or the mirror universe to push that message on paper is\na good way to build up that kind of resistance, to help build the sort of\nperson that could take Collective technology and democratize it. The sort of\nperson that could be the first, term-limited &quot;President&quot; of a Coöperative.\nThe kind of person that could stand up and say &quot;No more Queens!&quot; and mean it.</p>\n<p>(Agnes doesn't really make sense at all in S2.)</p>\n<ul>\n<li>The Borg Coöperative somehow uses a time loop to bootstrap an interestingly\nsized fleet; Borg and time travel are peanut butter and jelly</li>\n<li>Only one ex-Admiral can convince The Coöperative to join the Federation as allies</li>\n</ul>\n<p>There is a fun symmetry to be had in using <em>Star Trek: First Contact</em> style\nshenanigans to help strengthen the Coöperative (versus reduce the Collective).\n(But do it on purpose, with motivation! You don't spend nine-tenths of First\nContact's runtime wondering if the Borg are friend or foe and accidentally\nhelping them instead of fighting them.)</p>\n<p>On paper, the idea of Jean-Luc Picard being invited to be the ambassador to\nthe Borg Coöperative for the Federation also has some delightful symmetry.\nPTSD included. So far as I recall that's not even an idea that I think\nmuch of Beta canon considered, but it's an interesting one (that S2\nsquandered).</p>\n<p>There too, motivation would add <em>good drama</em>. PTSD gives Picard a lot of\nreasons not to trust the Coöperative, and have a tough time imagining\npeople that might volunteer for the sort of thing thrust upon him as\nLocutus. <em>But</em> if the audience has a good idea that the Coöperative is\nwho they say they are, <em>up front</em>, that's good dramatic tension, not just\n&quot;he's probably right, no one should trust the Borg&quot; which S2 suggests\nright until the last minute &quot;twist&quot;. It places Picard in the role not\nas the Ambassador friendly to both sides from the start, but the\nAmbassador doing due diligence to grow a proper trust (and maybe never\nquite getting there but still trying to at least do the right thing\nfor the Federation).</p>\n<p>But beyond the good bones above, I still wish that the reclamation Cube\nhad been more involved somehow in the S2 Coöperative. It was setup so\ninterestingly for that. I wish Seven of Nine was more properly involved\nin <em>electing</em> leadership. That also seemed interestingly setup but then\nnot paid out. I wish the Coöperative was less reliant on alternate\ntimeline resources and some sort of continuity drawn back to the Voyager\nepisode. Not because I'm a stickler to any of the Beta canon origin\nstories, but because a &quot;worker's coöperative&quot; should come from the same\nplace it is in rebellion against and there <em>is</em> such juicy fun in the\nidea that Voyager, of all ships, with no other help, accidentally seeded\nan entire faction of &quot;good Borg&quot; in the home turf of the Borg while just\ntrying to get home.</p>\n<p>It's been a long road, getting from there to here. I've continuously\nexplaining to people why I think S1 had some good ideas despite poor\nexecution and that S2 was worse because the Borg Coöperative is a\n<em>great</em> idea and it was so poorly executed most people have no idea\nat all, whatsoever, what &quot;Agnes' Borg&quot; were even supposed to be.\n(And they still might not even be the Coöperative. That's still\nmore subtext and showrunner tweets after the fact than anything\nsubstantive, especially since they were too busy to lend a hand\nat all in Season 3.)</p>\n","date_published":"2023-04-29T00:00:00.000Z"},{"id":"https://blog.worldmaker.net/2023/01/08/bluey/","url":"https://blog.worldmaker.net/2023/01/08/bluey/","title":"On Bluey, new PvP in Sea of Thieves, and Why It is Sometimes Fine to Suck at Warcraft","content_html":"<p>In November and December I had a maelstrom of different things smash together that all sort of reminded me\nof strange parts of my education, all the while I was fighting some illness that was mildly Flu-like. First\nchronologically, while I was in the middle of trying to win NaNoWriMo, <em>Sea of Thieves</em> released their newest\nPvP mode for which I have a lot of criticism to discuss. Then I was hit by that Flu-like thing just after\nThanksgiving and used that as excuse to binge watch all of <em>Bluey</em> that Disney+ had available. Whatever that\nwas had responded to anti-histamines so I'm mostly sure it was a perfect storm of different seasonal\nallergies hitting me all at once and a couple of them in ways I haven't felt in years, so I don't think it\nwas contagious but I was also trying for caution. Between the fevers, the migraines, and the binge watch I\ncame out of watching <em>Bluey</em> feeling somewhat like I was going to write a PhD dissertation on it or, deep\nenough in the fever, like I already <em>had</em> written a PhD dissertation on it. (Unfortunately, I don't know any\ncollege that awards Doctorates for virtual PhDs written about children's TV shows while running a fever, but\nI still kind of feel like I earned one and someone should award that to me.) Then deep in the middle of <em>Bluey</em>\nPhD brain I was nerdsniped by a Folding Ideas video that seemed almost pointedly wrong given the PhD\ndissertation I &quot;had written&quot;.</p>\n<p>It was a wild few weeks, and a lot of this I got into in somewhat real time on Mastodon and a few Discord\nchannels, but seemed something interesting to coalesce into something of a better narrative form to blog about.\nBeyond the through line of all of it happening in short succession chronologically, I think there's a lot\ninteresting cross-threads and maybe a useful overall narrative to all these various sorts of games criticism.</p>\n<h1>A Quick Recap of My Odd Academic Background</h1>\n<p>To give some context to everything, allow me to briefly recap my academic background a tiny bit. In undergrad\nduring my cooperative education internship with Microsoft I attended my first Penny Arcade Expo (&quot;down the street&quot;\nthat year from where I was living in Redmond/Bellevue). While attending PAX I was reminded that in High School\nI'd thought I would like to make videogames, that coalesced with some of the games criticism I'd been blogging\nat the time, and I caught something of an entrepreneurial bug there. That lead me to discovering a card game\nthat I loved, with great artwork, and from there working to license it to build a Xbox-playable version. I\ndecided that was going to be a big grad school project for me. My program was a targeted Master's Program and\nI had gone in with the assumption of finishing grad school (despite it being a degree that doesn't quite matter\nso much in the software industry as whole, it mattered to me at the time).</p>\n<p>I treated the entrepreneurship as its own grad school challenge, including academically. I used the small\nbusiness as an excuse to pick up a lot of game design textbooks, especially those covering MMO design for several\nreasons. The card game I was working on wasn't an MMO, of course, but was multiplayer. I had some ghost of an\nidea that given Adventure games were still mostly dormant outside of a few small developers that MMOs were an\nevergreen genre that if I could bootstrap into them could be very interesting. Here I had games like\n<em>Puzzle Pirates</em> as a particular influence at that time on ideas you could accomplish in a &quot;low fi, bootstrapped&quot;\nMMO. I've still never actually tried to bootstrap a low fi MMO, but I've still got lots of notes on various ideas\nfrom different years.</p>\n<p>So in all that I think I had a rather deep education of the state of MMO research and the &quot;classic&quot; textbooks of\nvideogame and game design in some brief moment in roughly 2007-2009. I capstoned that grad school work with a\nbrief chance to be a panel member on what I felt at the time to be a great PAX session in 2009. I failed to\npredict the sort of economy that would greet me after I completed grad school in 2008, and the card game I was\nworking on at the time was literally stomped on by Godzilla (which is funny now with distance, but of course was\nnot at the time when my expected livelihood sort of depended on it).</p>\n<p>That weird academic background I'm very proud of, but I've never made professional use of it. Perhaps it says\nsomething that my fever dreams thought I should apply it back to academics and go for an Imaginary Doctorate\nI wouldn't know how to use professionally either. I still don't know exactly where that came from.</p>\n<h1>Bluey!</h1>\n<p>Games are incredibly important to the (Australian originated) children's television show <em>Bluey</em>, almost every\nepisode is about games and even the intro to every episode is itself its own mini-game. I don't believe this is\na coincidence and I think this is underscored by the fact that the production company for the show decided to\ncall themselves Ludo Studio, with ludo being one of the Latin names for games/play, and the preferred one for\nthings like discussions of games theory (ludology) <sup class=\"footnote-ref\"><a href=\"https://blog.worldmaker.net/2023/01/08/bluey/#fn1\" id=\"fnref1\">[1]</a></sup>, and underpinning concepts such as ludonarrative\ndissonance (the difference, especially when troubling, between what the narrative, the story/theme, of a game\nis trying to say and what it's game mechanics are built to impart).</p>\n<p>Almost everything in <em>Bluey</em> is a game, including the intro, as I mentioned, follows rules, explores rules, and\ndeals with concepts like fairness in play, and fairness in rule design, and even complex things like\nludonarrative dissonance. It explores all those things, plus family and friendship and growing up and so much more,\nall in very tight 9 minute episodes. It's equal parts delightful (for instance, every time someone wins a game,\n&quot;Hooray!&quot;), and emotional (some things get sad for the talking dog families of Bluey's world). I don't expect\nanyone needs a recommendation from a childless adult who binge watched the show in massive single sittings as\ncomfort food during fever-like illnesses, but I loved it and strongly recommend it, and I would like to think\nthat it it is the kind of show that can both hold young kids' attention, and maybe teach them something.</p>\n<p>It deals with some of the problems of game design, and offers hints of ways to solve them. It tries to instill\nideas of fairness, of course, as many good children's shows do, but it also gets into some of the deeper things\nbeyond simple ideas of fairness in game play to why you need rules in the first place and what their goals are,\nhow to have fun playing with people that have different ideas of fun or games than you, and how to troubleshoot\ncases where people stop having fun. Overall these are great life lessons that apply equally to games and things\nthat aren't games. It is said that some of the games children play are deeply to &quot;rehearse&quot; real life. The\ndifference between real life and game play aren't binary, they are things that bleed over and between each other.\nThat's a strong theme of <em>Bluey</em> throughout and some of the most fun episodes leave you wondering how much\n&quot;magic&quot; was real life. You get the sense over three seasons that Bandit, especially, Bluey's dad, will do\n<em>anything</em> for a game and is possibly the best, most spirited games player so long as his kids are having fun,\nbut sometimes Bandit leaves you with questions of how much he's playing and how much is real (and that's part\nof the fun of the show). That's the power of a good game player sometimes. On the other side, many of the games\nthroughout the seasons have deep real world repercussions and many things are &quot;for real life&quot; (including, among\nother things, the friends you make along the way and the family you keep).</p>\n<p>While watching, I continuously had that weird feeling that so many episodes of <em>Bluey</em> showcased complex things\nfrom my weird grad school education but in relatable, easy to understand ways. That was at least part of where\nthe idea came from that I could spend hours lecturing on individual 9 minute episodes and how they relate to a\ntextbook's worth of ideas on how you design games, on how you run them, on how you should act as a good player\nof games. Somewhere along the way &quot;lecturing on it&quot; turned into &quot;writing a blog post&quot; (such as this on it) and\nthen became it's own little Imaginary PhD dissertation that I dreamed I maybe actually wrote. There would be a\nlot worse things in life than to hold a Doctorate in Ludology As Explored by the Children's Television Show\n<em>Bluey</em>. I think fever dream me earned it, and I probably don't have enough moonlighting time to try to do it\nin real life, but if there's a college interested in handing out that diploma, I suppose they could email me.\n(And as <em>Bluey</em> reminds us continually sometimes the things you earn in a game still count for real life. I'll\ntreasure the one I scrawled in metaphorical crayon for myself.)</p>\n<h1><em>Sea of Thieves</em> Has a New PvP Mode, Y'all</h1>\n<p>In November's Season 8, <em>Sea of Thieves</em> brought On-Demand PvP back as a game playing option. On paper, I think\nit is a great replacement for the lost Arena mode (and many people just call it &quot;New Arena&quot;, which I will have\ndifficulty myself not just referring to it as that), I like that it uses Adventure servers in interesting ways,\nand that there is room for it to better exist side-by-side in the same spaces as &quot;the rest of the game&quot; in a\nbetter way than the &quot;separate but not quite equal&quot; menu mode that Arena was. Yet in practice, I'm highly critical\nof it, how it is balanced, and especially how it is rewarded. It's very easy for this criticism to sound deeply\ncritical in the negative connotation sense that I don't like it and/or maybe even just hate it, so I suppose\nthat's why my brain spent a lot of time metaphorically blowing the cobwebs out of some old parts of my grad\nschool education and looking to find ways to constructively criticize it (in the proper denotation of criticism\nas not specifically negative).</p>\n<p>My harshest take has been that the new system is &quot;Emissary 2: Emissary for Masochists&quot;. The new system introduced\ntwo new Emissary factions that were clones of previous trading company/emissary factions but &quot;PvP Only now&quot;. It\ntook some existing concepts such as the Emissary flags and their relationships with factions and built new\nslightly different versions but &quot;PvP Mostly now&quot;. There's some good reasons to do that, of course, in that it in\ntheory started everyone off on an equal playing field and they felt that PvP was undervalued so having &quot;PvP Only&quot;\ncontent was a boost that they felt the game needed after lots of PvE fine tuning over the last few years (and the\nshutdown of the Old Arena).</p>\n<p>A useful lens for this to me remains the Bartle types. These were types of players as self-described on early MMOs\nand then compiled and studied by Richard Bartle among others. The Bartle types are sometimes considered an outdated\nor flawed methodology for several reasons: they were self-described, which can add some bias to the results, and\nthey are sociological profiles that are quite (intentionally) broad. At its worst usage, the Bartle types are &quot;MBTI\nor horoscopes for MMO players&quot;. At its best, however, there's still some usefulness in the way that Bartle types\ncan describe a &quot;four quadrant&quot; game that hits many key interests of multiplayer game players. Despite being\nself-described, early studies of MMO players showed an equal split among players of which of the four types they\npersonally felt most dominant, and that's still a useful rule of thumb property for first order approximations.</p>\n<p>I think my greatest criticism (as compared to my harshest take) about the new PvP system is not just that it is a\n&quot;one quadrant only&quot; solution, but that it feels a lot like a &quot;less than one quadrant&quot; system as it is currently\nbalanced and based on that self-described demographics &quot;rule of thumb&quot; it's the kind of thing that applies to\nlikely less than twenty five percent of possible players. We already know that was a problem with the Old Arena\nand why it shut down, in that it was used by fewer than 25% of the player base. The new system doesn't seem to\nrepeat some of the same mistakes that made maintaining the Old Arena expensive by sharing entire instances with\nAdventure and not needing different or dedicated servers just to run it, plus it isn't supposed to diverge from\nthe rule set in play in Adventure in any significant way and is designed to interact with existing Adventure\nrules rather than replace them. It maybe isn't the harshest possible take on the new system that in the long\nrun it may again be less than 25% of players using the system because it maybe <em>is</em> better designed to survive\nthat. I still find it is a useful to lens to discuss and criticize the system in this manner though, as\nparticularly my own interest in the system comes from a perspective primarily based in the other &quot;three quadrants&quot;.</p>\n<p>For brief recap: the Bartle types are generally referred to as Achievers, Explorers, Socializers, and Killers. A\nrelevant subset of Killers that isn't often considered a Bartle type of its own but has made its way into a lot of\nMMO discussions in general, the vernacular at large, and should rarely be ignored is Griefer. Bartle described the\ntwo axes connecting these four quadrants as Players versus World and Acting versus Interacting. In brief rough\noverview: Achievers primarily seek to act on the world (scoring points, winning things), Explorers primarily seek\nto interact with the world (finding hidden spots, solving puzzles), Socializers look primarily to interact with\nother players (hanging out, winning together), and Killers seek to act upon other players (killing them, of course,\nbut more generally just in getting some emotion back out of them). Griefers are a subset of Killers whose primary fun\nis getting specifically the maximum amount of <em>negative</em> emotions out of other players. (<em>Bluey</em> has great episodes\non nearly every Bartle type except Killers and/or Griefers. If the show needs suggestions on smart topics to bring up\nwith complex needs and real world repercussions, I offer the suggestion that there is rich ground there to cover,\nthough I also expect writing much on the subject for a children's show seems to me to be rather tough.)</p>\n<p>It's very clear that any PvP system is first and foremost primarily focused on the Killers quadrant. That should be\nobvious from the Bartle type name. I think an issue with the new system, because it is entirely opt in (for good\nreasons, of course, I'm not complaining that it is opt in) it winds up being able to capture the attention of only\na subset of Killers, for some of the same reasons that the Old Arena did. There was a lot of hope that I saw in the\nforums and Discords that the new PvP system would also pull a lot of the Griefers out of &quot;raw&quot; Adventure and push\nthem into PvP cycles instead. That personal goal of maximizing other player's misery for the Griefers generally\nmeans that they won't opt in for long (or much at all), because random ships doing normal random stuff in Adventure\nwithout marking themselves as &quot;interested in PvP&quot; is always going to be the more &quot;lucrative&quot; target for them and\ntheir needs for their idea of fun than other Killers that opted-in for sport and are prepared to lose and are\nprepared to minimize their losses.</p>\n<p>A somewhat related disinterest in the new system I've seen from some Killers I've talked to with the new PvP system\nis that the ranking system for matchmaking doesn't have a visible &quot;numbers go big&quot; metric for them to watch and\nbrag about like many other common Killer-focused games such as the <em>Call of Duty</em> franchises, <em>Rocket League</em>,\n<em>Fortnite</em>, etc. Many of those make matchmaking stats much more obvious, notable and sometimes directly and\nobnoxiously in their face. These are Killers mostly on the Killer-Achiever border that want that exciting\nachievement to brag about that directly reflects their Killer side. It somewhat makes sense that <em>Sea of Thieves</em>\nisn't making ranking numbers a part of the UI: they want to keep it somewhat secret sauce, because it needs to be a\ncrew ranking (team ranking) it by necessity has to be an aggregate across multiple players (which makes it harder\nto brag about personal scores), and in general <em>Sea of Thieves</em> tries to feel like an even playground/sandbox so\nsuch numbers are also somewhat counter to that spirit of play.</p>\n<p>I know the developers of Sea of Thieves tried to mitigate this somewhat with the &quot;Defensive&quot; option to play the\nnew systems, where it is most and worst like &quot;Emissary 2: Glowing Figurehead Boogaloo&quot;. This mode suggests that if\nyou put a target on your own back and go out doing normal PvE stuff to build up a hoard of loot to defend that you\nwill in theory gain other rewards faster (when you successfully defend your ship and hoard of loot). This was the\noriginal promise of the Emissary system itself: put a target on your back, gain faction rewards faster. To be fair,\nthe game even lets you stack both Emissary 1.0 and Emissary 2.0 for higher risk/reward.</p>\n<p>The original Emissary was balanced so that the target on your back is visible mostly only up to the edge of the\ncurrent horizon, unless you were representing the &quot;sometimes PvP&quot; Reapers faction in which case your target is\nalways on your back but you get potentially greater visibility of other Emissaries, eventually, if you do well.</p>\n<p>In contrast, the new &quot;only PvP&quot; factions and the new &quot;Emissary 2.0: Hurt Me, Please&quot; defensive mode leaves the\ntarget up on your back for all to see no matter what and remains always visible on the map. It adds the additional\nrisk of &quot;invaders&quot; that can spring out of the water at any time. (&quot;Invading&quot; is of course the other option from\n&quot;defending&quot;.) Additionally, the new Emissary cuts out most of the PvE-based reward gains unless you have specifically\nengaged in PvP at some point and won. This greatly increases the risk/reward, and so much so, that I have yet to\nfind anyone that thinks Defensive mode is worth the time investment. I knew enough players that were afraid enough\nto raise the old Emissary flags at all, even with the tighter horizon constraints, and the new one has far less\nappeal than even that. Killers, in my experience, want the easy/fast/on demand instant action of the &quot;Invading&quot;\nmode, and while the Achievers are given plenty of commendations to try to achieve in the new mode and Explorers\nand Socializers are teased with role playing-friendly social zones to eventually access/explore if they force\nthemselves to do enough of the new PvP mode, there doesn't seem to be quite enough PvE reward for all of the\nextremely high risk. If you are going to put the time to put in all the PvE work to build up a good loot haul,\nyou have very few reasons to do it with a target painted on your back at all times given the time investment.\nThe current short term rewards don't seem to align well versus the time investment, in my experience and\nanecdotally from other players' experience that I've heard.</p>\n<p>Anecdotally, from doing a lot of grinding of &quot;Invading&quot; very few seem like players actively defending a loot\nhaul and the vast majority seem like &quot;fellow invaders&quot;. Some of the more hopeful on the forums think this is\na temporary case, but I think from the Bartle lens above and examining the current risks versus rewards, I\nthink it is unlikely to ever shift, especially with the December mechanics rebalance. (The December mechanics\nrebalance added more ways to speed up the &quot;Invading&quot; cycle, further making it the key focus of the new system\nand further leaving me questioning who the &quot;Defending&quot; mode is for given its risk/reward balance.)</p>\n<p>My &quot;home&quot; perspective is from somewhere shallow into the Explorers quadrant in that on a given night I might\nlean into the Achievers side and feel the completionist bug, or I might lean into the Socializers side and\njust want to hang out with people. I can't say I've ever felt much of a Killer itch natively. (There is\nsometimes a Socializer peer pressure for a bit of blood lust and I'm not immune to that, but my primary fun\nin those cases still feels a lot more for me like &quot;socializing&quot; than &quot;killing&quot;.) I feel frustrated that cool\nnew social areas were opened to explore with seemingly impossibly high grind requirements for non-Killers.\nOn the one hand this certainly gives me a reason to play these new systems with a Socializer or Explorer hat\non, sure. On the other hand this makes me somewhat miserable and upset with the experience and while there are\nKiller-Socializers that might appreciate the new spaces, a lot of the Killer quadrant won't appreciate that\nsort of new content as much as other quadrants. The Bartle types as a lens also generally suggest that\nKiller-Explorers may be among the least likely minorities in multiplayer games because that pairing crosses\nnot just one but two axes at once. (Again, Bartle types are just a first order approximation of a player base\nand it's possible game metrics may even suggest it is a larger minority than that concept of abstract axes of\ninterest imply. Approximations remain useful for outside analysis though because I mostly don't have access\nto any <em>Sea of Thieves</em> statistics but my own.)</p>\n<p>Again, it is hard not to let my personal experiences sound too much like I hate the new systems on principle.\nI appreciate that true Killers have needed systems like this since the shutdown of the Old Arena and not\nevery update is about me or how I want to play. I've done lots of &quot;not fun&quot; grinds in Sea of Thieves and I\nlikely will again, that's the nature of my relationship with the game at this point. I just also am the sort\nof meta-gamer that I will do a semi-academic breakdown of the process and over-evaluate it, and then blog\nabout it, because that is fun in its own ways to me.</p>\n<p>As an Explorer, I've explored how repeatably badly I can intentionally <em>suck</em> at the new PvP to grind my way\ntowards its social rewards with the least emotional investment on my part (because that just gives me anxiety\nand isn't fun for me) and preferably the least amount of time investment. This is the meta-game I've been\nplaying with the new system. I think I have a system at this point, an algorithm of sorts that is just\n<code>do x, y; wait until z; do a, b; wait until c; repeat</code>. The completionist in me is haunted that this system\ndoesn't quite earn me a bunch of statistics for some additional commendations and I'll have to do a lot of\nthis grind <em>again</em> with crews of enough Killer hats anyway if I eventually care for completionism, but I\nnever completed the Old Arena either (and its achievements still haunt my Xbox, &quot;Legacy&quot; mark or not; I was\n90%+ in one but couldn't care enough before the Arena shut down and now that's just going to be forever stuck\nat the top of my &quot;nearly completed&quot; list in Xbox dashboards and a constant reminder in that Xbox sidebar that\nshows up when you pause a movie and the Xbox is close to sleeping, sigh).</p>\n<p>It's kind of fun for me at this point, boring rote repetition aside, seeing how fast and how often I can lose\nmatches. Though I've still got a large number of losses left before I get pity access to the new social areas.\nI've explored many of the ways to lose matches and narrowed down the ones that give any reward and then\nnarrowed down a few that give the most possible loss reward for the amount of time investment. (Again, I have\nbuilt a system for losing. I am maximizing my losses.) I am deeply amused at my growing count of accidental\nwins. (My current system does not involve firing a single cannon shot at an opposing vessel. Wins truly are\naccidents entirely out of my control at that point.)</p>\n<p>I've discussed my system with fellow completionists and some of them have been appalled. They don't want to\never grind just for losses. They can't imagine what that would do their rankings. They hate what that might\ndo to standings that they can't even see (because as mentioned above, that is not something the game is\ninterested in visualizing). But we can meta-game that a bit, too: So far the development team have stated\nthat a crew's ranking is a pretty straightforward average of each person's win/loss ratios. Someone with a\nlot of losses brings the crew average down and makes battles on average easier (when easier opponents are\navailable). I don't care about my PvP metrics and I'm more than happy to be the good luck albatross of\neasier battles. I'm not saying losing a lot is a great strategy in the long run, but there are certainly\ntactical advantages to it. It's okay to suck sometimes, especially if that's more fun for you and\nyou aren't a jerk about it. (<em>Bluey</em> has an episode or three about that.)</p>\n<h1>Why it is Sometimes Fine to Suck at Warcraft</h1>\n<p>Folding Ideas somewhat recently dropped an hour and a half dive into the Raid-focused and end game\nMetrics-focused culture in the MMO <em>World of Warcraft</em> called\n<a href=\"https://www.youtube.com/watch?v=BKP1I7IocYU\">&quot;Why it is Rude to Suck at Warcraft&quot;</a>. This dropped late\nin my <em>Bluey</em> binge and its Imaginary PhD dissertation and with the giant swirl of <em>Sea of Thieves</em>\nmeta-game thoughts above. I paused somewhere around nine minutes and forty seconds into the hour and a\nhalf pseudo-documentary and said to a Discord that was discussing it something to the effect that it was\na) poorly researched, and b) the entire hour and a half could probably be summarized by a single tight\n9 minute <em>Bluey</em> episode.</p>\n<p>I'm not that wide or deep in my &quot;pure&quot; academic background. Most documentaries I gloss over when\nreferences and citations fly by. I focused on hard, practical engineering work for the most part, including\nthat instead of doing a Master's Thesis I opted for a Master's Project as the more practical option. (I\nbuilt a simple &quot;game engine&quot; for analyzing swarm algorithm mechanics, a neighboring sub-field to machine\nlearning, in a light JS/Self-inspired custom language REPL designed specifically for very easy &quot;follower&quot;\nmechanics by default. Maybe not that practical outside of specifics to my advisors at the time, but more\npractical than the average Thesis.) But it turns out that there's one domain where you can directly call\nout my strange academic background: the one I already mentioned, games criticism and academia circa 2009.</p>\n<p>That roughly 9 minute stop in the Folding Ideas video felt like a direct call out to me specifically. I\njumped out of my chair at the very first citation and went back and paused it to reread it and make sure\nthat it was as bad as I had assumed. The video was quoting a 2006 &quot;text book&quot; that was among that deep dive\nI did in grad school (and I believe is in one of the many boxes of my personal library cluttering my den\nfor the last couple of years, because I keep procrastinating dealing with them). But it wasn't quoting it\ndirectly, it was quoting it from inside of a not directly related article. It was citing a quote without\nhaving directly read the book it was quoting, getting the quote itself only second hand. That's a huge\nacademic faux pas in general, but sometimes necessary when dealing with long out of print stuff. 2006\ndoesn't seem to qualify to me as &quot;long out of print&quot; (there are hardcovers for sale for as little as $6\nused on Amazon).</p>\n<p>The further you get into the video the further it becomes clear that their reading overall stopped well\nshort of many primary and secondary sources. Though to discuss why I think we need to first discuss the\nconclusion of the video and so this needs a massive <strong>spoiler warning</strong> for anyone looking to complete\nthe Folding Ideas video. I include a spoiler warning here because I've appreciated many Folding Ideas\nvideos before this one and shade aside to this one I wouldn't have spent 45 minutes watching a half-hour\nvideo at 2X speed, several Discord and Mastodon threads, and this much time writing up a blog post on\nsomething I didn't otherwise respect. I just want Folding Ideas to do better, I'm not interested in\nsending pitchforks in their direction. There is some good content meat in the sandwich of bad academics\nthat could have been better researched. Go watch that video if you are so inclined and come back to\nthis post unspoiled.</p>\n<p>The conclusion of the video is basically (and baldly) that videogames are soylent: they are made of\npeople. This should not be a shocking conclusion to anyone in the history of games. This wasn't a\nshocking conclusion in 2009 or 2006. This is three-fourths of what <em>Bluey</em> is about in almost any\ngiven episode. It takes people to play games. People bring their baggage along with them into the\ngames that they play.</p>\n<p>The weakest part of the Folding Ideas conclusion to me though is that it ends without any suggestions\non what to do about anything discussed in the video. It ends with roughly a noncommittal shrug. This\nis disappointing on a number of levels. The first one is because it took my immediate gut reaction\nthat I could substitute a random <em>Bluey</em> episode for the Folding Ideas (though it may take me longer\nthan an hour and half to explain why, as you can see here in this very blog post) and one ups it:\na random Bluey episode (or a half hour of them) would likely be a better use of time because they\noffer better starting grounds of solutions to the problem statement. <em>Bluey</em> gets into lots of\nsuggestions about how you engage with players with different ideas of fun and how do deal with\ninter-personal conflicts that arise in such situations. Certainly it delivers these suggestions in\nschool child terms, and a lot of <em>World of Warcraft</em> players would look immediately look down on any\nsuggestion that they should watch more of a children's show, but that's still a good foundation for\ndeeper discussions to start from than just shrugging at the question of &quot;What do we do about it?&quot;\nIt speaks directly to that conclusion the video thinks it makes: if games are people, the rules we\nbuild to &quot;play nice&quot; and try to get children to learn from an early age, apply at <em>every</em> age. To\nsome extent it really doesn't matter if you are playing &quot;Keepy Uppy&quot; or <em>World of Warcraft</em> if you\ncan't play nice maybe you shouldn't play at all. Those are eternal lessons to learn about human society,\nwhich <em>why</em> we work so hard to teach them to children.</p>\n<p>As nearly a tangent here from &quot;Imaginary PhD dissertation&quot; brain: one of things I found so powerful\nabout <em>Bluey</em> and why it resonated so much was exactly this. The show starts from a lens of games,\nand nearly everything in the show is about games, including the intro is a game, but the life lessons\nare all so very <em>for real life</em>. It is said that games are a key component to how kids develop life\nskills by starting in relatively safer spaces, but something that not enough people remember into\nadulthood is that while games are built to be &quot;safer spaces&quot;, they are &quot;safe spaces&quot; only <em>somewhat</em>\nfree from real life complications, and school of hard knocks real world life lessons. Games are one\nincredible part of how we train each other to better live in a society together, and the lessons we\nlearn both come from and extend back out to real life. (<em>Bluey</em> has many of its share of those same\nsorts of games that children play to learn society itself: &quot;Doctor&quot;, &quot;Shopkeeper&quot;, &quot;Parents&quot;, and so\nmany more. Again, with <em>Bluey</em>'s studio calling itself Ludo Studio I cannot imagine much of this is\nan accident, and I don't know it is my place to tell them &quot;well done&quot;, but I can suggest that at\nleast one fever-addled, childless adult was so impressed they imagined they wrote a PhD dissertation\non it.)</p>\n<p>It's not entirely a tangent, though, because it gets back into the narrative of why it seems like\nsuch a shame that Folding Ideas stopped so short in their own research. I know a lot of this about\nwhy <em>Bluey</em> seems like such an incredibly well done TV show to me precisely because of that weird\neducation I gave myself. The Folding Ideas video gave two reasons why they thought it best to stop\nshort. The first I think comes directly from that confusion between &quot;safer spaces&quot; and &quot;safe spaces&quot;\nand a second-hand criticism that the early literature focused possibly too much on &quot;safer spaces&quot;.\nA lot of the literature did explore the concept of &quot;ludic spaces&quot; as &quot;safer spaces&quot; and what that\nmeant about games, but most of it was <em>specifically</em> coming from that &quot;we think of games as\nchildish or child-like&quot; and directly defining how &quot;ludic spaces&quot; may seem &quot;safer&quot; at first glance\nfrom a raw risk/reward standpoint than &quot;real life&quot;, but absolutely exploring all the ways that\n&quot;ludic spaces&quot; are always still a <em>part</em> of real life and &quot;safer&quot; is never <strong>safe</strong> from real\nworld consequences nor real world harm.</p>\n<p>The very cover shown as Folding Ideas dismisses almost all of older research in this way is a book\nof first and second hand accounts of people's lives in <em>Second Life</em> and deeply proving that any\nattempt at &quot;second life&quot; is just &quot;real life&quot;. It gets into real life marriages that happened inside\nof and because of that game. It gets into people who tried to establish very real businesses in the\ngame and in some cases for some brief moment made it work. It gets into real life crimes that\noccurred in the game, some involving <em>Second Life</em>'s powerful scripting tools, and others involving\nold fashioned terribly human societal problems like trafficking. A book mentioned elsewhere in the\nvideo third hand but clearly not directly read got deep into <em>EverQuest</em> life and the societies it\nformed and again very real things like marriages and divorces. All of these and more were in\nconversation at the time with the situation that occurred in <em>Sims Online</em>. <em>Sims Online</em> was a real\nvideogame that EA spent millions of dollars to develop but never saw a single copy on store shelves\nbecause in the relatively few hours of its Beta existence it spawned an entire organized crime ring\nand allegedly had accusations that even got the US FBI's attention. That was the game that spooked\nEA so bad that they got out of MMOs for nearly half a decade (despite being an early and profligate\npublisher of them prior to that), and their lone running (though long-running) MMO today is\n<em>Star Wars: The Old Republic</em>. (A lot of history rewriters today claim that EA got out of MMOs\nbecause they couldn't compete with early <em>World of Warcraft</em>, but that narrative forgets key parts\nof history that we shouldn't forget. This isn't even a &quot;distant past&quot; problem, even just recently\na parent sued the videogame <em>Roblox</em> among other defendants for its part in facilitating dangerous\nreal world trafficking.)</p>\n<p>The other reason that the Folding Ideas video gives for why they stopped their research too short\nis &quot;<em>World of Warcraft</em> was the first game to…&quot; This is patently untrue for many reasons, including\nstarting from their own conclusions. <em>World of Warcraft</em> was never the first game to be played by\npeople. Compared to marriage, divorce, running a business, real world crimes like extortion and\ntrafficking, their concern of &quot;some people in WoW were mean to me because I wasn't using\nendgame-class/raid-level analytics tools and tactics&quot; feels utterly <em>banal</em>. It feels almost as\nchildish as those same WoW players would likely dismiss <em>Bluey</em> for being too childish and\ninapplicable to their circumstances, despite being almost the very sort of problem <em>Bluey</em> was built\nto teach about. Even extending that &quot;first game&quot; through the video's various qualifications and\nhedges: WoW wasn't the first MMO with deep scripting. <em>Second Life</em> as mentioned had user scripting\ncrimes even. Nor was it the first RPG game-like MMO with deep scripting. <em>EVE Online</em> has had more\nthan its fair share of mod tools (and Excel spreadsheets!), for longer than WoW. Reaching back\nfurther to text MMOs everything that the video talks about happening with mods and analytics tools\nhappened in the history of text MMOs. Text MMOs used Telnet, an unencrypted, dead simple\ncommunications protocol and there was no way for text MMOs to have any control of mods and analytics\ntools (and many also had server-side scripting tools as well). The history of text MMOs is also\nfascinating here because there was an &quot;other side&quot; of the hill that graphical MMOs still haven't\nquite hit (though ones like <em>Second Life</em> try): when everyone has scripting tools at their disposal\nat some point it stops being interesting endlessly data mining the existing content and there's a\ndeeper push to just build your own content. Many of the text MMOs with the most power in user\nscripting were also the most free in terms of possible content and how people could play/engage that\ncontent. There was little need for &quot;perfect tactics&quot; because there was always more content. Graphical\nMMOs have a while to go before we see that to any degree like text worlds saw (though for another\ninstance Cryptic Studios certainly has spent a lot of time exploring it with their &quot;Foundry&quot; efforts\nand tools in their various MMOs over the years as well).</p>\n<p>Academics is certainly a constant process of learning new things and applying what you learned in\nnew ways until eventually you discover a better idea of things. But that also doesn't mean that you\ncan just throw out all of the academics from before you were born or your favorite game was born. The\nFolding Ideas video's own citations imply several places where the team could have &quot;followed the links&quot;\nto earlier works that would have <em>enriched</em> the video and stopped just slightly too short to help even\ntheir own conclusion. Some of those books they skipped might have gone a long way to helping them come\nup with suggestions to make the game's culture friendlier than the shrug they ended with. Different\nplayers exist with different ideas of fun, those early books spent a lot of time trying to describe\nthat and coming up with very rough approximations such as the aforementioned Bartle Types, and trying\nto find ways to make more games appeal to &quot;all four quadrants&quot;. That many of the games they described\nare gone and long unplayable doesn't make them any less relevant today. In some ways it shows how much\nmore games need to mature that we so easily forget entire <em>cultures</em> of players, just as anthropologically\nlost as any empire lost to ruin, and often barely covered in a handful of first hand accounts in academic\nliterature, such as the ones this video referenced but skipped. It's all the more reason not to ignore\nthe first hand accounts that we do have, to not just stop at third and fourth hand criticisms, but\nread some of it for yourself.</p>\n<p>I also have few illusions that I've done much better than Folding Ideas at beating a random <em>Bluey</em> episode\non these topics. This post is nearly seven thousand words and its own far cry from a tight 9 minutes of\ntelevision. (It's also still a hundred or so pages shy of that combined Imaginary PhD dissertation I\ndreamed that I might have written.) I suppose the most useful takeaway is, go watch more <em>Bluey</em>. Also,\ngames are complicated and hard and full of real life and there will always be so much to say about them\nand so much already said about them worth reading.</p>\n<hr>\n<hr class=\"footnotes-sep\">\n<section class=\"footnotes\">\n<ol class=\"footnotes-list\">\n<li id=\"fn1\" class=\"footnote-item\"><p>Not to be confused with Game Theory (of Economics), though there is some obvious cross-over. <a href=\"https://blog.worldmaker.net/2023/01/08/bluey/#fnref1\" class=\"footnote-backref\">↩︎</a></p>\n</li>\n</ol>\n</section>\n","date_published":"2023-01-08T00:00:00.000Z"},{"id":"https://blog.worldmaker.net/2022/11/25/film-moment-slipstream/","url":"https://blog.worldmaker.net/2022/11/25/film-moment-slipstream/","title":"Film of the Moment: Slipstream (1989)","content_html":"<p>I realize that I fell out of the habit of &quot;of the Moment&quot; posts as so\nmuch of that sort of discussion moved to social media. I was reminded that it\nmight be fun to adapt this Mastodon thread that started as a\ndiscussion on Discord into a &quot;proper&quot; blog post.</p>\n<p>A Discord-posted meme of a tweet of how hard Mark Hamill looks in\n<em>Slipstream,</em> a 1989 rarely seen film, reminded me that the movie had\nbeen sitting in my &quot;To Watch&quot; pile for some time. I did not recall why\nit was on the &quot;To Watch&quot; pile, it might have been because it was a\n&quot;lost classic&quot;, it might have been as a &quot;so bad it is good 'classic'&quot;,\ngiven the age of how long it had been on my &quot;To Watch&quot; pile it might\nhave been something random like possibly people talking about the\ncredits of Robbie Coltrane around the time of his passing. I may never\nknow, I did not take good notes.</p>\n<p>The reminder that it was on my &quot;To Watch&quot; pile sparked the curiosity\nto re-check on it on JustWatch, which told me that Tubi had it free\nfor streaming (with ads) and with encouragement from that Discord\nchannel I decided to watch it.</p>\n<p>The was directed by Steven Lisberger best known for <em>Tron</em>, and was\nhis fifth and final film. IMDB says that the movie didn't get much of\nany US release in 1989 because the production had bankrupted the\nproducer, most famously a producer of Star Wars. Interestingly, it\ndoesn't seem to have been budget overruns or the other usual reasons\nfor a production to bust: that producer went through a messy divorce\nthat allegedly included awarding the ex-wife the royalties from Star\nWars which was exactly where the <em>Slipstream</em> budget was coming from.\nOops.</p>\n<p>I thought <em>Slipstream</em> wild and mostly fun. It was sometimes hilarious\nin its schlocky, trope-filled pulp dialog. My overall impression was\nthat it was a bit of a &quot;lost classic&quot; in that way that if I'd stumbled\nupon it on VHS in a hidden weird shelf at a Blockbuster at the right\nage in the 90s I might have loved the movie growing up.</p>\n<p>The closest comparison for several reasons seems to be <em>Waterworld</em>,\ndespite <em>Waterworld</em> having been produced after <em>Slipstream</em>.\n<em>Slipstream</em> plays as something of an &quot;Airpunk&quot; <em>Waterworld</em> (an_\nAirworld_? <sup class=\"footnote-ref\"><a href=\"https://blog.worldmaker.net/2022/11/25/film-moment-slipstream/#fn1\" id=\"fnref1\">[1]</a></sup>). It was equally a flop like <em>Waterworld</em> for budgetary\nreasons, but where <em>Waterworld</em> simply spent too much, <em>Slipstream</em>\nseems cheaper and overall probably well budgeted if the personal\nmistakes of the publisher hadn't interfered. <em>Waterworld</em> has a\nseriousness to it that plays corny, whereas <em>Slipstream</em> seemed to me\nto have an intentional playful corniness throughout (that trope-filled\npulp dialog, for instance), in ways that evoked to me a lot of old\npulp novels and radio/TV serials. (Much as <em>Star Wars</em> and <em>Indiana\nJones</em> mine those old tropes.)</p>\n<p>The opening narration introduces us to the idea that after a\nconvergence of terrible climate change disasters, most of what remains\nof humanity are only connected via a harsh air current known as the\nslipstream. (No need to wonder what the title of the film refers to.)\nThe other obvious type of film to compare this to is a <em>Mad Max</em>-style\n&quot;post-apocalyptic road trip adventure&quot;, with the interesting twist\nthat all the &quot;cars&quot; in <em>Slipstream</em> are gliders and small aircraft.\nThat gifts us a lot of great B-Roll and C-Roll footage of small planes\nthrough valleys in Ireland and Turkey for the film's version\nof a cave-filled, wind swept post-apocalypse. From the opening\noverture the score goes all out to sell these plane trips as\nincredibly important and maybe goes harder than it should, but I\ngreatly enjoyed that. Later in the movie those types of establishing\nshots also introducing the movie's few &quot;drop&quot; tracks, amusingly\ndiegetic in those moments and some equally harder than they needed to\nbe tracks.</p>\n<p>The movie is just full of some of the wildest (and most fun)\nperformances. The aforementioned Mark Hamill plays a blond-dyed\nfascist cop and seems to have great fun hamming it up as the primary\nantagonist of the film. (The above mentioned tweet was correct, his\nhair dye and the films costuming choices go a lot harder than they\nneed to, like he was a cut extra from a <em>Matrix</em> sequel, but work well\nin the context of the movie.) The primary protagonist of the film is\npre-<em>Twister</em> Bill Paxton having some of the most wild-eyed fun\npossible, getting some of the worst, most hilarious one-liner dialog,\nand chewing scenery along with it. Pre-<em>Jurassic Park</em> Ben Peck (as in\n&quot;Clever Girl&quot; Muldoon) plays the heart out of a role that is too easy\nfor me to accidentally spoil. There's a blink and you will miss it\ncameo from Ben Kingsley. Robbie Coltrane has the chance to steal a\ncouple of wild scenes. F. Murray Abramsom gets a strong scene. Just\nabout everyone in the movie seems to have the right idea of what sort\nof movie they are in.</p>\n<p>Strange caveats to mention:</p>\n<ul>\n<li>The violence isn't that remarkable for a PG-13 film, but the sexual\ninnuendo and near-nudity is kind of interesting. Some of the &quot;sex\nscenes&quot; were quite weird, had some crazy dialog that would have made\nparents I recall in the 90s super mad (but probably would have passed\nmy own parents' inspection at the right age, I think).</li>\n<li>There's a third act plot choice that feels very &quot;nearly a fridging&quot;\nthat feels somewhat problematic by today's standards.</li>\n<li>There's an &quot;enemies to lovers&quot; journey across the film full of some\nquestionable content, and what I thought was some questionably\nproblematic consent issues, especially in a late scene involving\nhandcuffs.</li>\n</ul>\n<p>Other than that it was a fun &quot;so (intentionally) bad it is good&quot; movie\nwith an interesting &quot;lost classic&quot; history and vibe to it.</p>\n<hr>\n<hr class=\"footnotes-sep\">\n<section class=\"footnotes\">\n<ol class=\"footnotes-list\">\n<li id=\"fn1\" class=\"footnote-item\"><p>Does that imply the eventual existence of an <em>Earthworld</em>\nand a <em>Fireworld</em>? No one will expect it when the <em>Fireworld</em> movie\ndrops. <a href=\"https://blog.worldmaker.net/2022/11/25/film-moment-slipstream/#fnref1\" class=\"footnote-backref\">↩︎</a></p>\n</li>\n</ol>\n</section>\n","date_published":"2022-11-25T00:00:00.000Z"},{"id":"https://blog.worldmaker.net/2022/10/30/angular-components/","url":"https://blog.worldmaker.net/2022/10/30/angular-components/","title":"I Have Tried to Set a Better Example for Angular Components","content_html":"<p><a href=\"https://blog.worldmaker.net/2021/06/26/angular/\">Previously I blogged about many concerns that I have about the sorts\nof examples that Angular sets (deep in its core libraries)</a> and how I\nfeel like it leads to a &quot;pit of failure&quot; when it comes to reliability\nengineering and performance optimization. In my day job I feel backed\ninto a corner where I have to support Angular application development\nand have had to become something of the performance expert and\nperformance &quot;officer&quot; against my early advice that we should have\npicked anything but Angular. In the end of that complaint post, to\noffer something constructive, I offered some bon mots about what you\nmight do if you were to rebuild Angular from nearly scratch (I called\nthat idea <a href=\"https://blog.worldmaker.net/2021/06/26/angular/#suggestions-for-a-better-angular-project-gawky\">&quot;Project Gawky&quot;</a> if you want to skip to it). At the end of\nthe day though, I'm a pragmatic software engineer. It's always my job\nto build my way out of problems, especially solving other people's\nproblems.</p>\n<p>The short story is that I've built a growing collection of libraries\naround what I've called the <a href=\"https://worldmaker.net/angular-pharkas/\">Pharkas Component Framework</a>. It codifies a\nlot my &quot;Observables-only&quot; best practices into what I hope is a &quot;pit of\nsuccess&quot; tool that's easy to slot into existing (&quot;brownfield&quot;) Angular applications\nand migrate things a component at a time as you can. I think it is\nincredibly useful out of the box and have been using it to improve\nperformance in production apps for months now. At the very least, I\nhope these libraries serve as a good example to the Angular ecosystem,\nwhether or not it sees strong adoption outside of production apps that\nI'm personally charged to &quot;grease the wheels of&quot;.</p>\n<p>I thought I would narrate some of the longer story as well. I'm very\nproud of <a href=\"https://worldmaker.net/angular-pharkas/\">Pharkas</a> and what it accomplishes as an example to get around\nwhat I think are  problems in Angular deep in the core libraries of\nthe framework, but I also feel like I need to provide at least as much\nmotivation and context as I can on why I built this to help answer why\nanyone should trust Angular libraries written by someone that\nunequivocally admits to hating working in Angular.</p>\n<h1>I Picked a Losing Fight With Zone.js</h1>\n<p><a href=\"https://blog.worldmaker.net/2021/06/26/angular/\">When I last blogged about Angular</a> I was obviously already trying to\nthink of constructive ways to build my way out of the mess. I\nmentioned these <a href=\"https://blog.worldmaker.net/2021/06/26/angular/#suggestions-for-a-better-angular-project-gawky\">&quot;Project Gawky&quot;</a> ideas in case they sparked someone\nelse to maybe put in the work, because at the time they mostly\nrevolved around replacing or somehow augmenting/extending Angular's\ntemplate language compiler. Angular likes to pretend that it doesn't\nhave a template language &quot;it just uses HTML&quot; and as you would imagine\nthis means that Angular's (massive) template compilers (there have\nbeen several massive rewrites) themselves are somewhat &quot;secretive&quot; in\nwhat of its internals are publicly documented. They aren't really\nbuilt for easy replacement or augmentation/enhancement. That lack of tools\nsupport is the core to why &quot;Project Gawky&quot; was much more of a\npie-in-the-sky rewrite idea than a pragmatic solution to offer.</p>\n<p>Soon after publicly documenting those thoughts on my blog, but while I\nwas still in the middle of thinking about trying to construct my way\nout Angular, I got tossed into a massive performance fight where the\nbiggest production app I was working on would just &quot;stall out&quot; for\n<strong>minutes</strong> of wall clock time. There was no noticeable network activity, no useful\n&quot;progress&quot; indication, terrible responsiveness to user interactions\n(&quot;slow&quot;/&quot;ignored&quot; clicks), and not even a &quot;please wait/processing&quot; beach\nball or spinning hour glass: it was just the absolute possible worst\nuser experience and it was making our production users angry.</p>\n<p>There wasn't a clear indication of when the problem started, much less\nif it was a performance regression specific to any recent code. (There\nwasn't even a clear indication of a specific source/cause. The\nreproduction was &quot;navigate the app randomly for long enough&quot;.) There\nwas some heavy calculation work in an observable pipeline that\nrecently was refactored just a tiny bit, so in terms of hypotheses,\nand enough evidence that pipeline was shared by enough components on most pages that\nwas my best idea of a place to start. I started in the obvious places\nof making sure that the pipeline wasn't over-subscribed, wasn't\nleaking subscriptions without unsubscribes, and wasn't accidentally\nover-observing to many input events from other pipelines.</p>\n<p>I started with a lot of <code>tap</code>s and <code>console.log</code>ging debugging, and in the\nmiddle of that was pointed to <a href=\"https://github.com/cartant/rxjs-spy\">RxJS-Spy</a> which is a fantastic debugging\ntool and I can't recommend enough. It provides a simple tag operator\nwhere you give a pipeline a name, which is a no-op in production\nbuilds but in debug builds gives you an entire dev console framework to\nspy on specific pipelines by name or groups of pipelines by regex. It\noffers the ability to choose between <code>console.log</code>ging and debugger\nbreakpoints. Again, it's just a great improvement on &quot;tap-style&quot;\ndebugging. Install it today. (I have nothing to do with <a href=\"https://github.com/cartant/rxjs-spy\">RxJS-Spy</a>, I just\nkeep recommending it to projects now.)</p>\n<p>The more I tagged with <a href=\"https://github.com/cartant/rxjs-spy\">RxJS-Spy</a> the more I verified that the app's\nobservable pipelines didn't have obvious leaks and were observing\nthings at a pace that seemed reasonable, including the massive\npossibly expensive calculations I was worried about in my hypothesis.\nAt this point I had easily disproved my hypothesis.</p>\n<p>This is the part of debugging that gives everyone nightmares: all of\nmy team's code is working just as expected. Does that mean the\nperformance issue isn't in my team's code?</p>\n<p>In just about any other framework I would have already have pulled out\n&quot;flame graphs&quot; from the Browser's performance developer tools and been\ntrying to base my hypotheses on real, hard evidence, not just shooting\nfrom the hip in the dark or trying to litter the entire code base with\n<code>console.log</code>s in the hopes that I could guess at performance\nbottlenecks. In Angular it is really hard to get useful data out of\nflame graphs for one specific reason: <a href=\"https://www.npmjs.com/package/zone.js\">Zone.js</a>.</p>\n<p>Zone.js is a supposed &quot;prollyfill&quot; to implement a JS proposed feature\nthat ECMA Technical Committee 39 (TC-39) shot down years ago for being\ndangerous, confusing, and not generally useful. So far as I'm aware,\nAngular remains the only &quot;customer&quot; of Zone.js, and today is entirely embedded in the Angular repo. Angular uses Zone.js\n<em>deeply</em> to power its change detection systems. Zone.js works by\nmonkey patching the entire JS world like a virus or other malware: it\ninfects every Event callback, every Promise, and every RxJS Observable. It\nplugs in a bunch of its own guts in the middle of every bit of code\nyou try to run in an Angular app.</p>\n<p>This &quot;infection&quot; is completely visible in any Angular production flame\ngraph. It changes and impacts every single execution stack in the\napplication. Look at the flame graph and the flames are all Zone.js.\n<em>You may insert here in your mind an &quot;Everything is fine&quot; meme with the dog labeled Angular and\nall the flames labeled Zone.js.</em></p>\n<p>Somewhere in those Zone.js flames your code is probably running. Somewhere.</p>\n<p>I captured some of these wall clock stalls in the performance tools. I\nknew to expect most of the flame graphs to be Zone.js nonsense. I\nassumed with the stalls taking minutes of wall clock time that\nsomething not Zone.js should be visible enough in that haystack to\nmake a difference and make it possible to find.</p>\n<p>I consistently found no needles in that haystack. I had minutes and\nminutes of call stacks and the further I dug in the more it was all\nZone.js haystack and not a <em>single</em> needle of application code. Was the\nperformance problem entirely Zone.js? I had no good ideas from the\nglimpses of internal-only Zone.js APIs and source files in the stack traces to\ntell in any reasonable way whatever it thought it was doing. (I still\nhave no good ideas or answers months later. Zone.js remains a\nterrifying horror mystery to me.)</p>\n<p>At this point in the horror movie (Happy Halloween! I guess you can\nnow guess why I was maybe saving this story for this month; it's a\ndebugger's ghost story) several audience members would be shouting at\nme: Zone.js has a debugger mode and turning it on is buried as a comment line in\nthe Dev environment.ts file in every template-scaffolded Angular\napplication, because they presume you will need it at some point. As a developer with a lot of experience in debugging, I find deep behavior changes between\nenvironments spooky. It was at this point where I felt that I was out of debug\noptions and I needed that frightening last option. I cautiously opened\nthat last door.</p>\n<p>With Zone.js in that weird debug mode, I could no longer reproduce the\npauses and the application performed better than production. 👻 Boo! It's\nhaunted! You're going to die! Get out of the house! 👻</p>\n<h1>Zone.js Must Die</h1>\n<p>This absolutely is one of my deepest nightmares as a sometimes\n&quot;performance expert&quot;: the bad performance is coming from inside the\nframework itself! The framework acts weirdly different in debug and\nproduction environments and it's the production environment\nexperiencing the worse performance in a way that makes no sense. You\ncan't debug your way out of the production problem because your\ndebugger can't reproduce it.</p>\n<p>Hyperbolically, I went <em>insane</em> here. I lost my damn mind.</p>\n<p>I had hard evidence that Angular was a horror show under the covers\nand was causing our production users real pain, anguish, and\nsuffering. But unfortunately, I don't have the power to convince an\nentire company that the Sunk Cost Fallacy is real and less of a\nproblem than trying to keep sleeping in the haunted horror house\nbecause we got such a good deal when we bought it from the previous\nowners who died of mysterious circumstances that surely were unrelated to why the house was on sale.\nI'm told to &quot;just do my job&quot; and patch a fix.</p>\n<p>That left to me the only &quot;logical&quot; and &quot;pragmatic&quot; realization:\n<strong>Zone.js Must Die.</strong></p>\n<p>I suppose in the horror film analogy this is the realization by the\nfinal girl that Zone.js really is some sort of serial killer and it is\ntime for her to roll up her sleeves and go on the offense and fight\nback against that ruthless serial killer.</p>\n<p>So I started researching everything I could to murder Zone.js without breaking Angular.</p>\n<p>Angular is kind enough to give you the option to boot up with a noop\n&quot;Zone&quot; and entirely disable Zone.js. Unfortunately, this breaks\nAngular Change Detection in weird ways and most apps stop functioning\nat this point if you just switch to the noop &quot;Zone&quot;.</p>\n<p>Angular's Change Detection apparatus is a direct consequence of\nAngular's broken compromises between providing RxJS Observables and\nthen also providing tons of imperative escape hatches. Observables are\nentirely &quot;push&quot;: they push notifications when changes happen. You\nshouldn't need change <em>detection</em> in a pure Observable world, you\nalready have change <em>notification</em> (&quot;for free&quot;), because that is what\nObservables <em>are</em>. (This is where the <a href=\"https://blog.worldmaker.net/2021/06/26/angular/#suggestions-for-a-better-angular-project-gawky\">&quot;Project Gawky&quot;</a> idea gets most\nof its promise: with &quot;free&quot; change notifications you can wire it to do\nsome very smart things also &quot;for free&quot;.) But Observables are &quot;hard&quot;\nand Angular couldn't commit to them and the resulting worst of both\nworlds compromise &quot;needs&quot; Change Detection.</p>\n<p>That Change Detection uses Zone.js to tell it any time anything\nhappens in the app, ever. Zone.js figures this out by wrapping all the\nEvents, Promises, and Observables in the world that it can find with extra\ninstrumentation. Just to tell Angular &quot;hey, something changed\nsomewhere, I don't know, maybe&quot; (not even really what changed, certainly not to\nthe specific level of individual Observable pushes). Angular still has\nto do a ton of work after those Zone.js callbacks to figure out what\nexactly changed and then from there what to update in the\ntemplates/DOM.</p>\n<p>Fortunately Angular seems to have actually anticipated this, too, that\nwith Observables you have a &quot;push&quot; based system for notifications\nalready and in theory shouldn't need change detection at all. It took\nme something of a deep dive into some of the less well documented\nparts of Angular, but it turns out the framework indeed has left\ncomponent developers a &quot;manual stick shift&quot; option for writing\ncomponents: you can annotate in the Component decorator that the\ncomponent uses the Change Detection Strategy named &quot;OnPush&quot; and that\nyou will push all change notifications manually.</p>\n<p>The &quot;<code>OnPush</code>&quot; Change Detection Strategy does give you an offense\nstrategy to use to fight Zone.js from the bottom-up of an application,\nand it needs to be from the bottom up: <code>OnPush</code> components do not need\nto be wrapped in Zones (and generally aren't, though Zone.js is\n&quot;viral&quot; in nature and there are no guarantees it doesn't accidentally infect), which is great. But\nthat also means that <code>OnPush</code> components can only ever use other <code>OnPush</code>\ncomponents. Components that use the &quot;<code>Default</code>&quot; change detection\nstrategy and need Zone.js to detect their changes can use <code>OnPush</code>\ncomponents just fine, but not the other way around.</p>\n<p>But a &quot;bottom up only&quot; hope in a brownfield application is still a ton\nof hope to make a noticeable change. A &quot;manual stick shift&quot; option\nisn't ideal, but that too gives hope that you have something that you\ncan automate and that you can build an automatic transmission on top of a\nmanual stick shift with software. It's not pretty, but it is\n&quot;pragmatic&quot; and it will get the job done.</p>\n<h1>Introducing the Pharkas Component Framework</h1>\n<p>To recap: I lost my mind in horror. I decided that <strong>Zone.js must die</strong>. Then I\nfinally discovered some hope for a &quot;bottom-up solution&quot;.</p>\n<p>I realized that I could build it: I could codify my &quot;Observables only&quot;\nway of building components into a library, and use that library to\nbuild a handy &quot;automatic transmission&quot; to replace Zone.js-based Change\nDetection with something smarter and less compromised (if it sticks to\n&quot;Observables only&quot;).</p>\n<p>Unlike <a href=\"https://blog.worldmaker.net/2021/06/26/angular/#suggestions-for-a-better-angular-project-gawky\">&quot;Project Gawky&quot;</a>, I had a firm place to start to build a useful,\nreusable library for building (Zone-free) <code>OnPush</code> components in an\nObservables only way that could provide not just automated push-based\nchange detection to Angular, but even bring in some of the &quot;smarts&quot;\nideas of &quot;Project Gawky&quot; and apply them as good defaults. By making\nthem good defaults I hope that my library can build not just a &quot;pit of\nsuccess&quot; but a &quot;pit of smart success&quot; to the developers that choose to\nuse it. For instance, React took several major versions worth of\nrevisions and refactoring and a lot of code to deliver &quot;concurrent\nmode&quot; which deprioritizes most DOM work until after idle callbacks such as\n<code>requestAnimationFrame</code> helping the browser to focus on interactivity\nover DOM element thrashing. Concurrent mode is still not yet the\ndefault in React for several compatibility reasons and needs to be\nopt-in. I've built something similar in my own library for debouncing\nchange notifications to Angular to nice clean <code>requestAnimationFrame</code> time just using Observable schedulers in\nvery little code (it's probably a lot more documentation than code at\nthis point), and it is default and (simple) opt-out. (While it is at\nit, the library also takes care of boring Angular administrative\ntrivia such as <code>ngOnInit</code> and <code>ngOnDestroy</code> lifecycle callbacks.)</p>\n<p>Overall, I feel like this library has turned into some of the best\ndocumented and well-tested open source I've had the pleasure to work\non. I'm not entirely satisfied with the testing just yet, as I'm\nwaiting for Angular to make the leap to the next major version of RxJS\nto get some good &quot;marble diagram&quot; timing tests added. Because of that\nuseful default of debouncing to <code>requestAnimationFrame</code>, I need a marble\ndiagram harness that understands and fakes <code>requestAnimationFrame</code>\ntiming, which the next major of RxJS supports out of the box and I\nwasn't happy with backports I attempted for the current Angular\nsupported RxJS.</p>\n<p>I named this library <a href=\"https://worldmaker.net/angular-pharkas/\">&quot;Angular Pharkas&quot;</a> and the approach the <a href=\"https://worldmaker.net/angular-pharkas/\">&quot;Pharkas\nComponent Framework&quot;</a>. This name is a terrible joke, that is possibly\nonly funny to myself. I had lost my mind, remember, and I needed to\nscrape out whatever sanity I could out of this entire horror\nsituation, <strong>and</strong> had to get whatever I built into production ASAP to make\nusers happy (and naming is indeed one of the hardest problems in all\nof computer science). So I named it a joke and filled its README with\na few jokes to amuse myself. It's maybe not the most &quot;professional&quot;\napproach, but sometimes we need humor in our darkest hours.</p>\n<p>To entirely over explain the joke:\n<em>Freddy Pharkas: Frontier Pharmacist</em> was a 1993 adventure game from Sierra On-Line near the\npeak of their development golden age. It can be described as the\n&quot;<em>Blazing Saddles</em> of videogames&quot; and is a joke filled satire of\ncowboy, Western, and Old West tropes in which the title character just wants to\nbe a respectable, civilized Pharmacist selling prescriptions in a lawless frontier town.\n(I recall it nearly breaks the fourth wall as hard as\n<em>Blazing Saddles</em> as well, but it has been a decade easily since I last played\nit. The comparison is not entirely unearned, for those that have a\nhigh opinion of <em>Blazing Saddles</em>.) As someone trying to peddle RxJS\nbest practices in a sometimes lawless-feeling ecosystem, I sometimes\nfeel like a frontier pharmacist when working in Angular. (The terrible\npun there being that &quot;Rx&quot; in addition to technically meaning &quot;Reactive\nExtensions&quot;, which was the original .NET name for its\nObservables-pattern framework, is also one of the more common\nabbreviations for the word &quot;prescription&quot; sometimes stylized ℞ and has been used by\npharmacists for that word for a long time, from latin &quot;recipe&quot; meaning &quot;take&quot;.)</p>\n<h1>The Growing Pharkas &quot;Family&quot;</h1>\n<p>Beyond the base component and the library that provides the core\n&quot;<a href=\"https://worldmaker.net/angular-pharkas/\">Pharkas Component Framework</a>&quot;, I've been slowly accumulating a lot of\nancillary libraries of other open source components and component base\nclasses that make sense to release next to it.</p>\n<p>So far the biggest running theme of these other components is\nproviding Angular wrappers for &quot;Vanilla JS&quot; components. There are a\nnumber of factors behind this including the sorts of components I've\nneeded to work on for my day job's production apps, navigating which\ncomponents are &quot;business critical/secret sauce/non-disclosable&quot; versus\nwhich seem good candidates to open source (or clean room rewrite as\nopen source in my spare time, because I lost my mind and have done some moonlighting here) because they have no domain specific\ncode, and that the &quot;bottom up&quot; approach to converting to <code>OnPush</code>\ncomponents especially highlights your &quot;VanillaJS wrapper components&quot;\nas a key &quot;bottom&quot; that needs conversion early.</p>\n<p>I think &quot;Vanilla JS&quot; components (and components from outside\nframeworks embedded inside Angular) are especially ripe to gain the\nbenefits of <code>OnPush</code> style components: they <em>shouldn't</em> have any\nchange detection needs because they handle everything internally.\nWiring all of a &quot;Vanilla JS&quot; component's Event handlers, Promises, and\neven Observables with Zone.js just because it may in very unlikely\ncases result in a change to detect is possibly the purest example of\nobviously unnecessary overhead. Zone.js <em>tries</em> not to be <em>that</em>\n&quot;viral&quot;, and most existing Angular wrapper components know the pain of\nwhat that means and all the little things that need to be wrapped in\nan <code>NgZone.run</code> callback. (<code>Default</code> components using <code>OnPush</code>\ncomponents don't need <code>NgZone.run</code> callbacks in my experience, that\nboundary is handled automatically enough, unlike the &quot;Vanilla JS&quot;\nboundary.)</p>\n<p>I think these Pharkas &quot;family&quot; of &quot;Vanilla JS&quot; wrappers should serve\nas useful examples of the gains to be made in using <code>OnPush</code> component\nwrappers in all cases. There should be no doubts that the performance\nis better in the boundary spaces between Angular and not-Angular.\nThere's no <code>NgZone</code> injections and no <code>NgZone.run</code> calls. There's no\nchange detection notifications necessary at all when the component\nhandles all of its own update cycles.</p>\n<p>I think they also serve as good, interesting examples of the types of\nsetup and teardown you can do when you think entirely in Observables.\nI think that's often one of the things developers complain the most\nthat they need imperative &quot;escape hatches&quot; from Observables for (and\nwhy Angular is the bizarre compromise that it is): dealing with the\nboundaries between components that understand them and those that have\nmore imperative APIs. You can do a lot with Observables if you put\nyour mind to it.</p>\n<p>These libraries are also more documentation than\ncode. Some of them are direct drop-in replacements for well known\nAngular wrapper libraries and I think you'll find less, easier to\nunderstand code than the wrappers that they replace, even before you\nadd in the additional benefits that they simply perform better.</p>\n<p>The &quot;<a href=\"https://worldmaker.net/angular-pharkas/demo/index.html\">demo site</a>&quot; for Pharkas right now is a collection of these\n&quot;Vanilla JS&quot; components themselves (more than one!) used in a combined\n&quot;dashboard&quot; with (fake) real time data. I'm incredibly biased here,\nbut I have never seen performance that strong at the &quot;Vanilla JS boundaries&quot; anywhere else in the\nAngular world. The real time is &quot;fake&quot; but modeled at speeds and\namount of data I've seen in actual real time dashboards in Production\n(in Frameworks that are not Angular). There's definitely no strange\nand unexpected Zone.js stalls. (The demo site is built in the Angular\nnoop &quot;Zone&quot; so truly has no Zone.js at all even in accidental\nfallback.)</p>\n<p><a href=\"https://github.com/WorldMaker/angular-pharkas\">All of this is MIT licensed open source</a>, and I encourage everyone to\nat least dig in and glance at the source and maybe try to learn from\nit, if nothing else, even if you don't think you need any of these\nlibraries in your own production work.</p>\n<h1>Aside: Observable &quot;State Management&quot;</h1>\n<p>The &quot;<a href=\"https://worldmaker.net/angular-pharkas/\">Pharkas Component Framework</a>&quot; is agnostic to how you build\nObservables, it only mandates that you <em>use</em> Observables.</p>\n<p>There are a lot of options in Angular, many inspired by the React\necosystem's Redux (in my opinion without understanding the reasoning\nbehind Redux, but that is a complaint for another blog post) such as\nNgRx, NgXs, and more. Pharkas doesn't care if you use any or none of\nthem. It works well with them. It works well without them.</p>\n<p>In my company's production we already have a wild hodge podge of all\nof the above. In my own development and prioritization I've taken a\n&quot;without them&quot; approach I currently call &quot;lots of small Observables&quot;\nwhich may be possibly called &quot;Atomic Observables&quot; in analogy to the\nReact &quot;counter-Redux&quot; term &quot;Atomics&quot;. Today I don't have a library to\noffer on this pattern. I see it as simply a pattern and an &quot;obvious\none&quot; at that, so I don't think it needs a library at all. (I might\neven describe it today as a &quot;natural&quot; pattern of Observable building,\nimplying it is the NgRx/NgXs/et al of the world that is perhaps a bit\n&quot;unnatural&quot;.) It is on my TODO list to eventually try to write some\nbetter documentation on that pattern in the hopes it sparks joy in\nsome developers.</p>\n<p>At one point I thought Pharkas &quot;needed&quot; a state\nmanagement answer or to be a little bit less state management\n&quot;agnostic&quot; to be a &quot;real&quot; Angular Observable library (thanks NgRx/NgXs\net al for that bit of impostor syndrome), but I decided YAGNI (you\naren't going to need it) and yeeted it out in early versions and have\nno regrets having done so.</p>\n<h1>Should You Use Pharkas?</h1>\n<p>I probably wouldn't if I were you. You likely have no good reason to\ntrust my claims at face value and doubt my credentials. I probably\nwouldn't even build this library, much less offer to maintain it if I\nconvinced my day job to avoid Angular like the plague, which I have\ntried to convince them multiple times. I don't blame you to be\nskeptical.</p>\n<p>Highlighted and self-aware summary of previous sections:</p>\n<ul>\n<li>Zone.js was built by developers in the lovely ivory towers of\nGoogle. They were confident enough in that effort that they proposed\nthat to actual standards bodies as a way that all future browsers\nshould work in perpetuity.</li>\n<li>The Pharkas Component Framework was written by an impostor\nsyndrome-filled dark matter &quot;Enterprise&quot; developer in a company that\nis not primarily a software company.</li>\n<li>This developer openly admits in a blog post that the framework came\nout of a fit of insanity.</li>\n<li>The Pharkas documentation files and even project name are full of\nunprofessional jokes.</li>\n<li>The Pharkas developer and maintainer has admitted to hating the\nAngular Framework in blog posts and claims that they will drop\nmaintenance support at first chance, depending on their day job's\nneeds. (On the other hand, <a href=\"https://github.com/WorldMaker/angular-pharkas\">it is MIT licensed Open Source on Github\nand easy to fork</a>.)</li>\n<li>Pharkas doesn't provide any sort of &quot;State Management&quot;.</li>\n</ul>\n<p>I scratched my own itch here. I solved some critical production\nproblems that needed solving ASAP, somehow or another. I did what I\nhad to do. This blog post isn't a plea to use this work. I have made\npromises that I think you would see clear performance gains and\nkinder, gentler developer experience if you to do that,\nbut I don't expect you to take my word for it.</p>\n<p>What I would like? Please learn from it! <a href=\"https://github.com/WorldMaker/angular-pharkas\">It's a handy, easy to explore\nMIT-licensed Open Source repository.</a> If there's one particular\ntakeaway here: use <code>OnPush</code> components everywhere you can! This truly is\na bottom up initiative. It needs to be &quot;grass roots&quot; in Angular, because it is not the default. Component\ndevelopers (<em>especially</em> those wrapping &quot;Vanilla JS&quot; components) should\nstart leaning <em>OnPush</em> component by their own default choice. I hope no one else\nexperiences the debugging ghost story 👻 I did to the same extent and it\ntruly is as rare as it seems in Angular that more people don't call\nAngular the &quot;haunted house framework&quot; or worse, but after having experienced\nthat every library I see in Angular I now evaluate on &quot;does it use\n<code>OnPush</code> components?&quot;. You don't need <a href=\"https://worldmaker.net/angular-pharkas/\">Pharkas</a> to build <code>OnPush</code>\ncomponents. I think Pharkas makes it very easy to do that, and adds\nsome nice &quot;automatic transmission&quot; and smarts on top of it, so I would\nrecommend taking a look using it to build your next components, but\nagain my plea here is only for using <code>OnPush</code> components, I don't care\nhow you get there (&quot;manual stick-shift&quot; or not).</p>\n<p>I hope I have set a good example here.</p>\n<p>Happy Halloween, and good luck if you are an Angular developer. 🎃</p>\n","date_published":"2022-10-30T00:00:00.000Z"},{"id":"https://blog.worldmaker.net/2022/08/17/dont-grow-old/","url":"https://blog.worldmaker.net/2022/08/17/dont-grow-old/","title":"Don't Grow Old","content_html":"<p>I wrote what follows this foreword on July 9, 2018. I was sitting with my sister at the bar at the\nrestaurant where my brother worked. It was some of the earliest evening hours the restaurant was\nopen and there was hardly anyone there. We both were drinking as we were waiting to catch my brother.\nOur grandmother had passed away. We were there for each other, to spread around some hugs, and to\ncry into some bourbon, as just one stop in a busy grief tour that always used to be expected to come\nfrom a family's funeral week. (2020 told us not to even take <em>that</em> for granted, of course.)</p>\n<p>The title of this essay came easily at the bar that evening (though it still felt like afternoon)\nand I recall the text flowed out quickly behind it. I think I finished it before I finished my\nbourbon that afternoon. That's how I remember it at least.</p>\n<p>My father believes a lot in sharing (and preserving) memories and especially loves to fill times of\ngrief with impromptu group circles of memory sharing. Most usually in such situations I feel under-\nprepared like I forget to do the homework and I find it stressful and forget any useful memories and\nrarely have much to add. I appreciate everything about it, and I love my father for it, even if I do\nslack off on the &quot;homework&quot;.</p>\n<p>This essay felt to me like one of the first times I was ever prepared with the homework, and yet the\nopportunity never quite arose to read it publicly. That's what I had written it for. Some of that is\ncertainly on me for not speaking up louder that I had something prepared and wanted to read it. Some\nof that is just the blur of all of that and way that grief gets processed and everything else going\non. It's funny in its own little way that the one time I felt prepared was a rare time where I didn't\nfeel I needed to be.</p>\n<p>I'm sure I'm still processing this grief, as it's been a weird four years. I thought something that\nmay help a tiny bit is the small closure that would be from at least publishing it publicly, since\nit was intended as a public address, though presumably in a admittedly in more intimate setting like\na family dinner. There two it's funny in its own way that actually doing that in the moment felt\nscarier than publishing it in the somewhat less intimate setting of a blog post.</p>\n<p>Other than adding this foreward, I've decided to leave this essay almost entirely as it has been\nsitting in my notes for four years.</p>\n<hr>\n<p>&quot;Don't Grow Old.&quot; It was a constant reminder from my Grandmother. An honest statement, certainly, but there was always a wink to it, a dry understanding and deadpan humor.</p>\n<p>Grandma was our babysitter for so many important events in life. She watched us during the births of siblings. She watched us those nights a few weeks before Xmas when mom and dad needed a date night, but more importantly a night to buy all the presents to spoil us with. I believe it was Paige that figured out that pattern, used that in her eventual hunt for the presents before the day to open them. Don't grow old.</p>\n<p>In college, grandma was a source of reliable, cheap meals, but more than that she was an important sounding board for the stresses and anxieties of engineering school. She'd helped several of my uncles, her sons, through that of course, but the best help she provided here wasn't specific advice. It was the conversation and it's patterns. One of them was always: Don't grow old.</p>\n<p>Grandma was the best listener I know. She always paid attention and cared deeply about every word you said. She'd ask pointed questions to know everything about it, your thoughts on it, and how you were doing otherwise.</p>\n<p>Over the years there were many conversations with my grandmother. Lazy Sunday afternoon chats in the den, and late night chats over dessert in the kitchen. We'd talk life or basketball or whatever would come to mind. Sometimes we'd grouse about politics together. She had a dark, wicked deadpan, and in the middle of the conversation would drop some ridiculous bon mot of the day from talk radio with a straight face and it would take a few moments to catch on. She found that hilarious. I found that hilarious, though that hairpin from shock to humor was tough sometimes, she was great about timing that. I've had to apologize for enough similar deadpan jokes over the years that I know I picked up the bad habit, and the sometimes terribly calculated timing from her. I never did understand the appeal of those talk radio shows though.</p>\n<p>When I allowed the stresses of my freshman year of college to spiral to the point where I knew that I was failing at least one course and felt unlikely able to continue, Grandma was the first person I felt I could confide in. I didn't always expect any direct answers, but I knew that the good questions she would ask would get my head around where I was, what my path forward was going to be, how I was going to possibly face my parents at all. Perhaps the closest thing to deep advice I'm sure she gave me then might only have been &quot;don't grow old,&quot; which if it is advice, it's always too late when you hear it. Yet at the end of that conversation I did feel so much better, and I did feel that I knew where I was, and what I was going to do. I did tell my parents next. I fought for it, and got two degrees, and made my grandmother proud. I certainly grew up and out of that funk, I'm not sure about old. I hope not.</p>\n<p>It's a deadpan joke where the punchline is terrible. &quot;Don't grow old.&quot; Grandma said it nearly to everyone, but I sometimes felt like she particularly said and meant it to me a lot. I always wanted to think that maybe that's because I was in on the joke, shared that sense of humor, that realize that all you can do is laugh, even though it hurts. Yet, it was always a reminder, too. Not in the Peter Pan sense of &quot;Never Grow Up&quot;, there was never anything wrong with growing up, growing into responsibilities, learning from past mistakes. Don't grow old, don't let it make you a worse person. Don't let the pains of age and time harden you from the experience.</p>\n<p>I'd like to think that I was in on the joke, but I realize that maybe I just more often needed that reminder. I had a chance to have one last conversation with her, late the other night in the hospital. Despite the circumstances, despite the situation, I could almost imagine it was a continuation of any of those other late night family chats in her kitchen. As I contemplated all the chats I would never have with her, all the stresses and anxieties she wouldn't be there to ask pointed questions about, she made sure to remind me, &quot;don't grow old.&quot; Hah.</p>\n","date_published":"2022-08-17T00:00:00.000Z"},{"id":"https://blog.worldmaker.net/2022/07/04/twenty-years/","url":"https://blog.worldmaker.net/2022/07/04/twenty-years/","title":"Start of 20 Years at WorldMaker.net","content_html":"<p>I'm starting into the 20th year of my blog being hosted on this\ndomain, which is an anniversary I chose to celebrate in my usual\nfashion by refreshing the blog's theme. I'll get into some specific\nthoughts on that later. LCARS Moderne &quot;2.0&quot; won't look drastically\ndifferent to irregular visitors to my website, but has a fresh coat of\npaint and takes into account six years of feedback (and web browser\nchange).</p>\n<p>Technically, it is only the 19th anniversary at the domain, because of\nhow anniversaries work, but the off-by-one big round number of 20 has\nstill caught me somewhat feeling reflective of the passage of such\ntime anyway. While it is an anniversary I mostly just celebrate with\nnerdy bits of CSS and HTML, it's still so tied into different eras of\nmy life. While some of that history is lost in the current archives,\nfor better and worse, there are still lines I can see in this blog's\nhistory. For examples: there's the point where I stopped publishing\npolitical opinions because I got tired of fighting over them, and\nthere's the point where I stopped posting so many short posts because\nTwitter was built, and those two lines nearly but don't entirely\nintersect.</p>\n<p>Most of my Twitter posting history is in a zip file at this point as I\nfelt that I had to delete it from Twitter itself. Sometimes I wonder\nabout backfilling short posts and &quot;best of archives&quot; into the blog. I\ndon't know what is in that zip file that remains relevant out of\ncontext. That's part of why I deleted it off Twitter, because time\nrobbed so much of it of context and robbed of context they were\nliabilities I could no longer as easily control. I was a bystander in\nsuch an attack on old out of context posts, and saw enough to leave.\nAdding them to the blog could give them new context, but I'd have to\ncurate them first.</p>\n<p>Similarly, I know where the line is where I removed direct commenting\non my blog. Comments were something that I felt important so early in\nthe blog. It was feedback and community. Of course, back in those days\nbefore Twitter and Facebook and &quot;social media&quot; that was what blogs\nwere for. The last comment platform I used was Disqus and I dropped\nthem between privacy concerns with how they were treating data and a\ndesire to reduce JS on my blog to a bare, optional minimum. (Not\nbecause I have anything against JS, it's often a huge part of my day\njob, but because it sometimes gets in the way on a blog in performance\nand flow of reading.) I've got a dump of all the comments in a zip\nfile, too. Some of those I recall were good discussions and I think\nsometimes I should post an archive of at least the better ones, back\nin context of the posts they referred to. Other times I wonder if it\nis better that those comments are gone, the slates clean. It feels a\nbit like the same sort of for better and worse forgetfulness that\nwiped several years of college posts from the blog. Some of them were\ninteresting time capsules and others were worth losing for the pain\nand/or problems they caused alone.</p>\n<p>I can still see so many lines that marked ambitions damaged and/or\nmajor project failures. I joke these days that &quot;WorldMaker&quot; as much as\nanything today is the name of my impostor syndrome. It comes up as a\n&quot;joke&quot; more often than I'd like because I still use the handle as my\nprimary in gaming and on Discord, out of decades of momentum at this\npoint. I still mostly remember why I picked it, so many years back in\nhigh school on IRC and in forums. In some ways it <em>is</em> weird to still\nbe using it most of a lifetime later.</p>\n<p>I've never quite been as prolific at short fiction as I tried to be in\nhigh school. (The loss of a few short stories is what I mostly regret\nin the holes in the blog archives.) I've only posted a couple of short\nstories in the past few years. On the other hand I may have been\ngetting better at longer forms of fiction writing. I've won NaNoWriMo\ntwice in the last few years. One novella was a game of pronouns and\nmultiple narrators that test readers didn't like (and I couldn't blame\nthem; it was an intentionally experimental game), and the other is a\nwork of fan fic over on AO3 for now. It's been too long since the last\nattempted game I finished writing, and I've got experiments in the\nforge, but no idea if they'll ever be more than just experiments.</p>\n<h1>LCARS Moderne 2.0</h1>\n<p>The basics of LCARS Moderne are largely the same: at typical tablet\nwidths and wider it is a Star Trek inspired library interface with a\ncurved C-shape around the main content area, which still scrolls\nhorizontally by default (with JS enabled; more on this in a bit).</p>\n<p>Most obviously, I refreshed the colors, now with palettes that are\ninspired by the color choices of Star Trek: Lower Decks. On the\ntechnical side I moved the palettes from build time SASS variables to\nrun time CSS variables. Dynamic colors were features I put too much\nwork into previous blog designs, so it's something I appreciate about\nthis upgrade (and how much easier it is today, just swap a CSS class).\nI tried to be subtle with it, and some pages now use different\npalettes based sort of on their function/context. It's not quite the\nevery page load uses a random color scheme of some of my (lost) past\ndesigns (back when that involved so many image files to accomplish).</p>\n<p>For a fun example of the more dynamic nature of the color system, a\nred alert toggle button:</p>\n<button class=\"button is-danger\" onclick=\"document.body.classList.toggle('redalert')\">\n    Red Alert!\n</button>\n<p>I revised a lot under the hood, in part based on six years of\nfeedback, and in part based on changes in how browsers displayed the\nsite over six years.</p>\n<h2>Still Horizontally Scrolling Along</h2>\n<p>So the big thing is that by default I still wanted it to be a\nmulti-column layout like a newspaper that scrolls horizontally on any\nscreen wider than narrow (bigger than a phone screen). I know it is\nweird, and I've seen a bunch of hateful comments over it, but it's a\nretro-future weird that I like and this is a personal site that's\nallowed to be personally weird. I've got an ultrawide monitor on my\ndesktop these days and I like that I have just about the only site on\nthe web that I can full screen on my screen and the site naturally\nmakes good use of all that width. I can read entire articles sometimes\nwithout scrolling, just reading it newspaper style from column to\ncolumn. I still think that is neat.</p>\n<p>I did try to address some of the &quot;affordance&quot; issues that had been\npointed out over the last few years. (Affordances are things that make\nit more obvious and/or easier to work with.)</p>\n<p>Six years ago it used to be that multi-column layouts, when in a\ncontainer that scrolls horizontally took the column width you\nspecified as-is and ignored the view container size, so a\nnon-percentage width would cause columns to be cut-off/bleed-through\nthe edge naturally. I thought that was a useful affordance for\n&quot;there's more to scroll&quot; if in most default browser sizes there was\nalways a bit of column peeking out across the edge to encourage\nscrolling. Since then the browsers have all converged on a behavior\nwhere the requested column width is now just a minimum size and even\nwhen the container scrolls horizontally it still tries to balance\ncolumns so that they fill the viewable area of the container\n&quot;perfectly&quot;, removing that hint of a column just slightly cut off or\njust slightly peeking in that there's more to scroll. I'm not a fan of\nthis newer behavior the browsers converged to, but I understand why\nthey did that: the new behavior is what more people want, because\nfewer people actually want horizontal scrolling and they'd rather\nthings look more &quot;perfect&quot;.</p>\n<p>I tried to add an affordance back here by adding a &quot;column rule&quot;\nbetween the columns. I still don't <em>want</em> a column rule, as I don't\nthink it matches the aesthetic I'm going for, especially with how\nlittle options there are for how a column rule looks. It is limited to\nan early subset of CSS border styles that feels like a throwback to\nme. I wanted to pick something unobtrusive that kind of blended into\nthe background, but I also wanted to pick something that maximized the\n&quot;affordance&quot; capabilities of the column rule here, as its one of the\nfew remaining obvious hints that &quot;there is more to scroll&quot;. The rules\nonly get drawn in between columns, so if you see one at an edge at\nall, it does imply that there is more to scroll. But I was worried\nthat might be too subtle on its own, so I increased the size of the\nrule and chose a pattern that is more obviously &quot;cut in half&quot; on the\nedges. I played with the sizes until I reached a point where I thought\nthat effect was obvious but not distracting. I'm still not entirely\nhappy with the column rule, but I think I've done about the best I can\nwith the current CSS tools.</p>\n<p>Six years ago there was a non-standard CSS flag I could use to ask the\nbrowser to let the scroll wheel scroll the page horizontally. Because\nit wasn't a standard and only supported by a couple of browsers it\neventually stopped working. So I've added a tiny bit of JS that takes\ncare of it now. When the container is scrolling horizontally the JS\ncode treats scroll wheel scrolling from your mouse as if it was\nhorizontal direction scrolling rather than vertical. This does\ndrastically make it nicer to scroll articles again (if you aren't full\nscreen on an ultrawide or are on one of the longer articles).</p>\n<p>While I was touching it, I also added a CSS class to toggle back and\nforth between the horizontal scroll and vertical scroll. I also added\na not-subtle-at-all button with JS to toggle that and also point out\nthe current scroll direction. It's a lot less subtle than the old\nunaligned columns or the new kind of subtle column rule. It might be\nsubtle that it also acts as a toggle button, if someone really, really\nwants it to scroll vertically &quot;like normal&quot;. I don't recommend that as\nthe best experience on most browser sizes. I still have text justified\nat all sizes, which I think looks great in multi-column and phone, but\nwon't look as good at larger window sizes scrolling vertically. I'm\nalso making the choice to not cap the width when it scrolls vertically.\nSo many websites do that today. It's often seen as &quot;necessary&quot; to keep\npeople's increasingly wide screens from making text regions too wide to\nbe easily readable. My preferred solution to that is multi-column\nlayout and horizontal scrolling, as clearly mentioned here, so if\nsomeone chooses to scroll my site vertically, I'm going to leave it to\nthem to manage the width of their browser without me making a strong\nCSS statement on that.</p>\n<p>Both of those JS features, the scroll wheel translation and the toggle\nbutton seemed usefully necessary affordances that at this point it is\nthe JS itself that &quot;activates&quot; the default horizontal scrolling, so if\nyou don't have JS enabled you get the vertical scrolling. I still\nthink of this increased amount of JS as &quot;optional&quot; in itself, but\ngiven the horizontal scrolling now &quot;requires&quot; it, it's certainly again\nthe case that the best experience of my website is with JS\nenabled. Few people disable it to notice these days (as opposed to my\nover-indulgences in JS in my youth), but it's a consideration I\nmade all the same.</p>\n<p>On that front, I did a bit of &quot;CDN elimination&quot; moving away from third\nparty hosts for code, images, and fonts and moving things back to\nhosted locally only on the site. That should eliminate all third party\ntrackers on this site now. (I don't have any first party trackers as I\nstopped caring years ago, and I would assume GitHub likely has &quot;second\nparty&quot; trackers in its GitHub Pages hosting.) I moved to a bundling\nand install process for LCARS Moderne 2.0 updates, which is kind of\nneat behind the scenes trivia. (esbuild and npm with a bit of robocopy\nif you are curious.)</p>\n<p>I'm proud of this blog theme and I suppose I expect it to last another\nsix years at least. I expect I'll hear plenty more feedback on it still.</p>\n","date_published":"2022-07-04T00:00:00.000Z"},{"id":"https://blog.worldmaker.net/2022/05/07/visit-sea-thieves/","url":"https://blog.worldmaker.net/2022/05/07/visit-sea-thieves/","title":"About \"A Visit to the Sea of Thieves\"","content_html":"<p>In November for National Novel Writing Month (NaNoWriMo) I wrote\n<a href=\"https://archiveofourown.org/works/38014378\">a novella about the Sea of Thieves</a>.\nI posted the whole thing to AO3 which seems the natural home for it\nas a work of fan fiction. I still haven't decided if I want to also\nserialize it to my blog, but I did want to blog about it and where it\ncame from.</p>\n<p>This novella, <em>A Visit to the Sea of Thieves</em>, is my second NaNoWriMo\n&quot;win&quot; across many attempts. The first (final working title was <em>The\nSpace Train on the Way to the McMaster Heist</em>) was an interesting game\nof dual unreliable narrators experiencing the same events (planning a\nheist while living as the hospitality crew on a space train) from\ndifferent life perspectives and also a weirder game of pronouns (most\nof the crew used they/them, characters change pronouns midway through,\nsome pronouns were chosen to intentionally be clashing beside\ntraditional familial relationships). Those games kept it interesting\nto write, I felt like they were in service to the story, and helped\ncontribute to making it a &quot;win&quot; in NaNoWriMo, but the beta readers I\ngot (great) feedback from didn't like it very much and I entirely\nunderstand why that would be. So far my best idea for reworking it to\nbe more readable involves adding a third, maybe more reliable,\nnarrator and while that makes sense in my head, I think on paper it\nwould just add more confusion.</p>\n<p><em>A Visit to the Sea of Thieves</em> also did not impress some of the beta\nreaders I tested it with, because it a work of fan fiction, and\nbecause there is a stylistic choice I felt necessary to make, but I\nwill get to that. I posted it to AO3 in case maybe someone has nearly\nas much fun reading it as I had writing it, but I'm not sure who that\naudience is beyond just myself.</p>\n<h1>I Am a Big Fan of Sea of Thieves</h1>\n<p>I've been a regular player of Sea of Thieves since its launch. There\nare multiple blog posts in my archives here where I go on at length\nabout Sea of Thieves topics. You can also see the threads of why Sea\nof Thieves would appeal so much to me in even earlier (and still\ndiscoverable) posts about games like Puzzle Pirates. I love the pirate\ntheme. I love the cooperation aspects of crewing a ship together with\nother people.</p>\n<p>I've developed a lot of weird skills across a lot of regular play. I\nmay not be the best contributor to PvP on a ship, but I can helm and\nsail (solo if need be) any ship in the game. (Sometimes that need is\nstrong: the rest of the crew is busy and you need a ship from one\nplace to another in a hurry.) At one particularly weird point in my\nplay I got absurdly good at dual controller play while soloing a ship.\nThere aren't a lot of good reasons to need to multibox in Sea of\nThieves, it was mostly for the challenge of it at that point. It hurts\nmy head thinking about trying to do it again, but I had some fun with\nit and it would probably come back to me as useless skill that it is\nlike riding a bike and patting your head at the same time.</p>\n<p>At helm, I've basically memorized the entire map at this point. Give\nme an island to get to and I will often sail there without needing to\ncheck the map or ask for a heading. I can generally provide headings\nby scanning the horizon and getting a feel of nearby island\nsilhouettes again without needing to reference the map.</p>\n<p>On islands, I've become known as a nut for gold hoarding. Give me an X\nmarks the spot map and I'm generally on the Xs first dig attempt. I've\nbasically memorized most common riddle spots and can run riddles at a\nrelatively quick pace. I've had nights where the entire crew were on\ndifferent islands (while I was on helm of the ship) doing different\nriddles and each riddle step I was able to offer useful headings to\nspeed run the riddles to the crew member at each island over Discord\ncomms. There are many puzzles in the game that I know intimately and\ncan solve quickly.</p>\n<p>I've become known as something of most ship's Lore Master. I've read\nthe books and I follow the in game storytelling. I've gotten real deep\ninto the game's Tall Tales. (I earned the gold curse doing the\nmajority of it solo the hard way in the time before checkpoints were\nadded to them. I fought many players and kraken to get those\ncompletions.)</p>\n<p>Writing a novella about the Sea of Thieves seemed like an easy &quot;win&quot;\nfor NaNoWriMo given how much of the lore I can explain when prompted,\nand how much of the map of the Sea of Thieves just lives rent free in\nmy head at this point.</p>\n<h1>I Have Been a Big Fan of Uru (Myst Online)</h1>\n<p>A lot of my lore hound nature has been built and tested in other games\nover time and Uru was a strong fandom for me. I'm sure there are blog\nposts to be found on that subject.</p>\n<p>A core conceit to the lore of Uru was always that the events of the\nother Myst games were (somewhat apocryphal) legends of events that\nhappened sometime in the 19th Century (the 1800s) and Uru was &quot;real&quot;\nand player actions happening &quot;now&quot;. You, an Explorer among many peers,\nwere personally taking a trek out to the desert in the American\nSouthwest, finding a trail of directions to a peculiar cleft in a\nmountainside, solving puzzles, and from there making a winding journey\ndown below the surface into an extraordinary Cavern full of ruins of a\nlost civilization.</p>\n<p>Uru fandom was full of many different players' journals of their\nexplorations of the Cavern, and though there are many places to reach\nin the game of Uru outside of that cavern in one way or another,\ngenerally the whole fictional landscape was described as &quot;in cavern&quot;\n(as opposed to real life events out of cavern; the overlap with more\nusual uses of the abbreviations IC and OOC amusing and intentional\namong that fandom).</p>\n<p>Some players' journals got surprisingly detailed. Sometimes because\nthey became guides/walkthroughs. Sometimes just because that's how\nNPCs were generally written, too, and the example was set. Sometimes\njust because players had fun imagining the work they put into their\nexpeditions to the American Southwest, the gear they'd pack and the\nthings they need to do (for safety), as if they were planning a caving\nexpedition to Mammoth Cave in the early years of its exploration or\nsome other similarly famous cave. A lot of the drama in cavern was the\npush/pull between the organization set up to try to keep people safe\nin their explorations and to do respectful restoration (the D'ni\nRestoration Council [DRC]) versus the people that just wanted freedom\nto explore without an oversight organization tell them not to do\nunsafe things. Some of the irony in players' fun journals of their\nexplorations and how careful they planned their expeditions was\ndirectly contrasted with an early puzzle that relied on every player\neither having forgotten to pack something as common to cavern\nexploration as a flashlight or some variation of losing their\nflashlight or not having enough batteries or something (and the DRC\nwas apparently quite stingy with their lovely branded cavern exploring\nhelmets with attached flashlights).</p>\n<h1>The Fun Intersection of Sea of Thieves and Uru Lore</h1>\n<p>One of the things that you pick up over the years as a Sea of Thieves\ncrews' &quot;Lore Master&quot; is that all of the official lore (such as the\ncomics, the novels, etc) happened years before players arrived in the\nSea of Thieves. Most of it happened some time near the real world\nGolden Age of Piracy in the 18th Century (1700s). (But also some\nancient civilizations older still than even those events.) Meanwhile,\nfor very similar reasons to Uru, events that the players are\nparticipating in are &quot;real&quot; and happening &quot;now&quot;.</p>\n<p>Like Cyan Worlds with Uru, Rare wants to give Sea of Thieves players\nthe feeling like their choices matter and their actions are really\nhappening in the scope of the lore. Unlike Cyan Worlds, however, Rare\nhave tried to take a strong stance against any lore happening &quot;now&quot;\nthat the players aren't directly contributing to and &quot;playing&quot;. There\nare no NPC journals of &quot;today's events&quot; for players to discover.\nAlmost all of the &quot;current events&quot; lore today is word of mouth lore\npassed among players in game and on Discord and social media. There\nare NPC quest givers for some events, but in general they leave almost\nall of the interpretation and the choices to the players. There's a\nnobility of purpose there that I respect. There's a weird joy in\nfeeling like your play matters to the overall storyline. (There was\nnearly an entire year of events that contributed into building up the\nresources of a less than respectful faction and as as a completionist\nevent player during those events I remain amused how complicit I have\nbeen in the troubles that faction has since caused, the repercussions\nof which current events have been fighting.)</p>\n<p>Unlike with Uru, there's a lot less of a journaling culture around the\nevents in Sea of Thieves in quite the same way as Uru. With fewer NPC\nexamples and not as much of the player base even aware that what they\nare doing is happening &quot;now&quot; (which should explain things like Halo\nand Gears of War themed liveries). There are some equivalents: so many\nof Sea of Thieves events and culture are captured daily on Twitch. I\nloved reading the science reports of Merfolks Lullaby. (Their weather\nreports especially on Sea of Thieves have been quite valuable.)</p>\n<p>But as far as I'm aware there was a big sort of hole there that I\nthought would be fun to explore: how do you plan a visit to the Sea of\nThieves &quot;now&quot;? What does that expedition look like? How did you, a\nwannabe pirate, fall into making such an expedition?</p>\n<h1>Planning <em>A Visit To The Sea Of Thieves</em></h1>\n<p>With my Uru experience the premise of telling that sort of expedition\ntale seemed &quot;obvious&quot; to me, especially because it would be so much\nharder and weirder for Sea of Thieves. It's easy enough to understand\nhow Golden Age Pirates crossed over to the Sea of Thieves, but much\nmore complicated to figure out how you might expect players to cross\nover today. Relatedly, it's easy to imagine preparing a trek to the\nAmerican Southwest for spelunking alone or with friends. People do\nthat relatively all the time. I've certainly enjoyed my random tourist\ntrips to Mammoth Cave. Even though I take somewhat regular vacations\nat this point to the Caribbean it's still a lot more complicated to\nimagine trying to find a hidden sea in the Caribbean, much less doing\nso in relatively piratical garb and with an 18th Century style wooden\nship. I thought there was a lot of fun spaces to world build what that\nwould be like and why it would exist and at least some of the sorts of\npeople that might be crazy enough to do it. So I decided to try\nwriting a &quot;conspiracy thriller&quot; about visiting the Sea of Thieves.</p>\n<p>The one stylistic choice that felt like a natural constraint given\nRare's assertion that events that happen &quot;now&quot; should be actions of\nplayers (that left some of my beta readers unhappy) was to write it\nentirely in Second Person. This is the language of a\nChoose-Your-Adventure novel or an Interactive Fiction work where\neverything happens to &quot;you&quot;. It's something I've grown up with from a\nyoung age and love. Outside of these play spaces, Second Person is\nextremely rare as an art form. (There's two Charlie Stross books I can\npoint to in the science-fiction mainstream today, at least.) I had\nEnglish teachers in school that loathed Second Person deeply and tried\nto squash my love of writing Second Person tales. I appreciate that\ngiven how rare it is outside of CYA and IF that I got immediate\ndislike reactions from some of my readers. I'm sorry for that, and I\nunderstand exactly where that gut reaction can come from.</p>\n<p>I never did find counter threads in the narrative I wanted to tell to\ntry add interesting and meaningful choices and make it an actual\nCYA/IF novel, but I considered it while drafting. I think it is why\nthe working title stuck through to completion in the form that it\ntook, it's <em>a</em> (single) visit to the Sea of Thieves, one of possibly\nmany. While I didn't write more than one, I intended too, but then got\ncold feet.</p>\n<p>Beyond Uru, another influence I might mention is that I'd just\nrecently before November binged For All Mankind. I think that\ninfluence is pretty obvious when you get to it, but I felt I'd mention\nit as a great binge watch since it so far has escaped many people's\nradars.</p>\n<h1>Aside: Signal Flares</h1>\n<p>Something that amuses me was that in trying to write myself a world\nbuilding problem, I accidentally predicted a new real game feature\nthat showed up to players later in November.</p>\n<p>I wanted to give the impact of a Discord alliance communication\nin-game with the in-book restriction that there's no Discord\nconnection on the other side of the Devil's Shroud (while in the Sea\nof Thieves) so I figured signal flares would easily fit the game's\naesthetic and allow some distant communication. Rare added signal\nflares into the game inventory alongside fireworks in the following\nupdate after I'd already started making that a key part of my alliance\nfleet's action plan in the novella.</p>\n<p>I like that &quot;great minds&quot; feeling, though some may not believe me I\npredicted it ahead of time. (I do somewhat regularly play Insiders\nbuilds where things are tested ahead of time to collect cosmetics in\nthe main game. I was busy writing the novella that month and missed a\nfew weeks of Insider play. I also tend to avoid spoilers in my Insider\nplay as much as I can reasonably do it. My usual MO in Insiders is &quot;to\ntest if basic Gold Hoarding still works&quot; and friends know I tend to\nrefer to it as &quot;riddle hour&quot;. I really do just grab the most basic\nGold Hoarder voyages and grind X mark the spot and riddle maps just\nbecause I find it fun and relaxing and mostly avoids spoilers of new\ncontent/mechanics.)</p>\n<h1>Feedback Welcome</h1>\n<p>So yeah, I wrote a fan fic novella mostly to fill a need I saw in the\nlore that would amuse me. I hope there's at least one reader out there\nwith enough of a cross-over to appreciate the novel as much as I\nenjoyed writing it. I also hope it is a fun read whether or not\nsomeone has Sea of Thieves lore knowledge or not. As fan fic I'm\nletting it's primary home be AO3 for now.</p>\n<p><a href=\"https://archiveofourown.org/works/38014378\">A Visit to the Sea of Thieves</a></p>\n","date_published":"2022-05-07T00:00:00.000Z"},{"id":"https://blog.worldmaker.net/2022/01/16/blockchain/","url":"https://blog.worldmaker.net/2022/01/16/blockchain/","title":"The Technical Basics of \"Blockchain\"","content_html":"<p>Trying again to break down that the technical basics of &quot;blockchain&quot;\nare devastatingly simple, because people trying to convince you that\nblockchain is really complex technology are <em>trying to sell you\nsomething</em>, so I've broken it down into a lot of little &quot;chat line&quot;\npieces, and tried to make sure that every technical term is explained\nas it is used (or isn't entirely relevant) with the intended audience\nbeing folks with no technical background. (I think anyone with a\ntechnical background should find this explanation sufficient: &quot;A\nblockchain like Bitcoin is a rebase-only git commit process of a\ntransaction ledger where each new commit hash must start with enough\nzeroes. The committer with the right commit hash is awarded more\ntokens to use in transactions in later commits.&quot;)</p>\n<p>I tried to reduce the number of places where someone can &quot;Well,\nactually&quot; me with a technical quibble and have kept the biggest ones\nto parenthetical asides that should be truly optional to the\ndescription as a whole. I'd like to especially note here that: while\nit is very easy to make such &quot;Well, actually&quot; interjections in just\nabout any technical description of anything, <em>nuance does not imply\ncomplexity</em>. Especially in this case it implies <em>variation</em>. Not every\nuse of &quot;blockchain&quot; is Bitcoin, and there are certainly plenty of\nvariations both like and unlike Bitcoin today, but Bitcoin is and\nlikely will remain &quot;king of blockchains&quot; (and a use of\nelectricity, all by itself, continuing to be larger than the\nelectricity produced by entire &quot;second world countries&quot;) so it remains\nthe strongest example to use of the basics of how a blockchain\noperates, technically speaking.</p>\n<p>I hope it helps. &quot;Proving&quot; that blockchains are devastatingly simple\nisn't going to change hardly any minds on their stance towards\nblockchains and cryptocurrencies. I just hope I've helped do my part\nto break down some of the salesmanship that drives cryptocurrency hype\ncycles (&quot;it is so complex you just cannot understand it, so <strong>trust\nme</strong>&quot;). But I'm a cynic here and think it is far too late, the majority\nof the damage is done, and that hype cycles or not, for better and for\nworse, &quot;blockchain&quot; is here to stay, and mostly in my opinion for the\nworst.</p>\n<hr>\n<p><strong>Block:</strong> you've got a bunch of documents to store that rely on/link to\ndata from previous documents</p>\n<p>One of the easiest ways to &quot;link&quot; such documents is to record in\n&quot;child&quot; documents a secure ID of the &quot;parent&quot; document</p>\n<p>A common form of &quot;secure ID&quot; is to use the output of a &quot;cryptographic\nhash function&quot;, called a hash</p>\n<p>A hash is a relatively tiny number that reflects the contents of a\nlarger document</p>\n<p>What makes a hash &quot;secure&quot; is when you can't predict the hash of a\ndocument, but the same document will always produce the same hash, you\ncan't predict the contents of the document from just the hash, and\nsimilar documents produce unpredictably different hashes</p>\n<p>(That's the difference between any old &quot;hash function&quot; and a\n&quot;cryptographic hash function&quot;, what the word &quot;cryptographic&quot; means\nhere: that it meets those security needs of unpredictability)</p>\n<p>When using a cryptographic hash function in this way to link\ndocuments, this data structure has been called a &quot;Merkle tree&quot; since\nthe 1970s</p>\n<p>It's natural shape <strong>is</strong> a tree: more than one document can point to the\nsame parent, there's nothing in the nature of this way of storing data\nthat prevents that from happening</p>\n<p>In the case of most cryptocurrencies what is stored in the documents\n(inside of &quot;blocks&quot;) is the transaction ledger: Transfer X amount from\nAddress 1 to Address 2, Love Address 1</p>\n<p>Transactions depend on previous data (need to point to parent\ndocuments; reason it forms a Merkle tree): at some point previously in\nthe transaction ledger enough transactions sent X or more to Address 1\n(and Address 1 has not already transferred that much out) so that it has\nX to send to Address 2 (and is a valid transaction)</p>\n<p><strong>Chain:</strong> creating artificial scarcity by &quot;pruning&quot; the (Merkle) tree</p>\n<p>Merkle trees may have an infinite number of possible branches</p>\n<p>Blockchains use a &quot;consensus algorithm&quot; to choose which branch is the\npreferred branch, and which other branches to ignore</p>\n<p>(Forks are still in the same conceptual Merkle tree: the difference\nbetween the Bitcoin, Bitcoin Cash, Bitcoin Gold, etc branches is much\nmore <em>marketing</em> than technical)</p>\n<p>(Artificial scarcity: there's still an infinite possible number of\nbranches or &quot;forks&quot; allowed by the nature of the underlying data\nstructure, but marketing is great at selling &quot;preferred options&quot;)</p>\n<p>&quot;Consensus algorithms&quot; among other things declare &quot;which document must\ncome next&quot;</p>\n<p>The most common &quot;consensus algorithm&quot; (and arguably the only known one\nthat seems to work in the long term so far) is Bitcoin's &quot;Proof of\nWork&quot;</p>\n<p>&quot;Proof of Work&quot;: the next document (block) proves you have done a lot\nof &quot;hard work&quot; in order for it to count in this branch</p>\n<p>(In computing terms &quot;proof of [hard] work&quot; really is just &quot;prove you\nspent enough electricity&quot;)</p>\n<p>In Bitcoin's case the &quot;work&quot; to prove is completely stupid: the\n&quot;secure ID&quot; for the block needs to start with enough Zeroes</p>\n<p>This is <em>hard</em>: because the secure ID is a secure hash (from a\n&quot;cryptographic hash function&quot;) and the output hash is supposed to be\nentirely unpredictable from the input data</p>\n<p>The only currently known way to do this is to add a bunch of random\ngarbage next to the document (which is what truly makes it a &quot;block&quot;:\ndocument of transactions, plus random assortment of garbage) and see\nif you get enough Zeroes starting the hash (ID)</p>\n<p>Not enough Zeroes? Add different random garbage and try again (this is\nall the &quot;work&quot; in &quot;proof of work&quot;, randomly generating literal\ngarbage)</p>\n<p>First one with enough Zeroes &quot;wins&quot;</p>\n<p>(Everyone calls this &quot;mining&quot; and not &quot;pruning&quot; because the by-product\nof a &quot;win&quot; is new coins to spend; to incentivize computers to sit and\nspin creating random garbage in search of finding all these &quot;useless\nzeroes&quot; it's a very dumb lottery)</p>\n<p>(You can't spend coins without &quot;mining&quot;: &quot;mining&quot; is what adds [and\nreconciles] your transactions to documents in the Merkle tree\nbranch/blocks in the &quot;blockchain&quot;)</p>\n<p>There's a name in security speak for this sort of hunt for a specific\nlooking hash (ID): a preimage <strong>attack</strong></p>\n<p>(Technically this is a <em>partial</em> preimage attack because Bitcoin\ndoesn't care what the rest of the ID is after all the zeroes it wants\nat a given time; but from a security perspective any partial attack is\nstill an attack)</p>\n<p>That's where Bitcoin's &quot;Proof of Work&quot; especially transcends from not\njust &quot;stupid&quot; but downright &quot;evil&quot; because it is a rapid, heavy attack\non a cryptographic hash function used in a lot of other places as a\nbuilding block of internet security</p>\n<p>If someone found a shortcut that wasn't just the brute force &quot;build\nrandom garbage&quot; (why I prefaced with &quot;the only currently known way&quot;),\nthey found ways to predict the outputs of the cryptographic hash\nfunction forever weakening their usefulness as secure IDs</p>\n<p>(This has happened before. We've lost cryptographic hash functions\nwhen we've found out that they were susceptible to preimage attacks.\nBut in those cases we had decades of warning and known better\nalgorithms waiting in the wings. If Bitcoin miners truly &quot;win&quot; the\npreimage attack we might not have any warning at all and a lot of\ninternet security would be immediately at risk. It's also arguable if\nwe have known better algorithms waiting for such an eventuality\ntoday.)</p>\n","date_published":"2022-01-16T00:00:00.000Z"},{"id":"https://blog.worldmaker.net/2022/01/05/pub-vinaigrette/","url":"https://blog.worldmaker.net/2022/01/05/pub-vinaigrette/","title":"\"Pub\" Vinaigrette","content_html":"<p>I heard once that you can tell someone's commitment to veganism by\ntheir collection of oils and vinegars. I'm not sure how true that is,\nbut I do love a vinaigrette and Good Seasons carafes have taught me\nthat it's very easy to experiment with vinaigrettes to get extra\nflavors, whether you use a Good Seasons &quot;Italian Dressing&quot; packet or\nnot. I appreciate Good Seasons as a simple brand that isn't afraid to\nlet you explore. Just in exploring the vinaigrette possibility space I\nhave been collecting a wide variety of oils and vinegars.</p>\n<p>One of my recurring experiments has been with a &quot;Pub&quot; Vinaigrette. The\nbasic idea was that I sometimes love a splash of Malt Vinegar on a\nfish and chips meal like many do, and I was trying to figure out what\nsorts of vinaigrette you could make with it. I was especially trying\nto tap into something with the spirit of &quot;this is a salad dressing you\nmight find to partner with fish and chips as one big pub meal&quot;. I\nthink of it the house dressing of a weird pub trying to source a\nvinaigrette mostly from ingredients at hand or at least &quot;close to\nhome&quot;. Of course, Malt Vinegar has a very strong flavor profile so\nfinding the other elements to pair with it was tough and took some\nplay/experimentation.</p>\n<p>I didn't think Olive Oil was right to pair with Malt Vinegar, and you\nwould be wasting good olive oil taste in the way it would disagree\nwith the Malt Vinegar I felt.</p>\n<p>You could use a boring Vegetable or Canola oil, and that might be\nappropriate given the boring oils typically used for frying fish and\nchips. I nearly stopped at that almost fine solution.</p>\n<p>The discovery that really cracked the experimental project wide open\nfor me was Avocado Oil. Avocado Oil has a rich and nutty flavor, not\nentirely what you would expect thinking about guacamole, but a flavor\nto its own. It's a strong flavor that doesn't get rolled over by Malt\nVinegar like Vegetable Oil would, nor do I think it clashes quite like\nOlive Oil would. I think it adds a &quot;chips&quot; feel, almost the bite of a\nnuttier potato, when paired with the malt vinegar.</p>\n<p>The big variable in my experiments so far have been been the amount of\nmustard that I've added. I think this &quot;Pub&quot; Vinaigrette works well\nunseasoned without mustard. I think a hint of mustard makes it more\ninteresting. I've been working up to seeing how much mustard I can\nadd. Because I love strong mustard, I want more mustard salad dressing\noptions that aren't a cream or dijon (honey mustard or not). I\nassociate Colman's Mustard with fish and chips night at a pub\nsometimes too, from some appetizers that come to mind, and keep\nwondering how much I can dial up &quot;Pub&quot; Vinaigrette towards that goal.\nSo far I've not been disappointed with my experiments. In my most\nrecent attempt I used almost a third or more of a entire shaker of\nground mustard, believe this may definitely be a dressing where the\nmore mustard the merrier. With that much ground mustard mixed it\nturned out almost lighter than the color of orange juice (despite how\ndark the vinegar and oil both are), and when the dressing separated\nbetween uses (and reshakes) it made a lovely &quot;sunrise&quot;.</p>\n<p>The one variable that remains on my idea board is that though I\ndismissed Olive Oil as the base oil in theory I like the concept of\nusing a splash or more of olive brine (possibly in place of some of\nthe water content?), which occurred to me as an interesting nod to a\nDirty Martini. I've not actually tried this yet, though.</p>\n","date_published":"2022-01-05T00:00:00.000Z"},{"id":"https://blog.worldmaker.net/2021/10/20/future-day-reminder/","url":"https://blog.worldmaker.net/2021/10/20/future-day-reminder/","title":"Tomorrow is Future Day 2021 (Positive Sixth Annual)","content_html":"<p>October 21st each year I like to order a pizza and a Pepsi from\nPizza Hut, watch all three <em>Back to the Future</em> films, and think about\n&quot;The Future&quot;. I call it <strong>Future Day</strong>. It's a relatively simple holiday\nto celebrate every year, and I've celebrated it for decades. October\n21st, 2015 was the date that Doc takes Marty McFly and Jennifer to\nvisit their future. I've always counted the holiday from 2015, so this\nwill be the (Positive) Sixth Annual Future Day, even though I've celebrated it\nfor decades at this point. I even went so far as to post the idea to\nFacebook and invite guests to my place to hang out and marathon\n<em>Back to the Future</em> with me for a few years early in owning my current\nhome. I don't remember the exact years offhand, but that would have\nstarted sometime round the Negative Fourth Annual.</p>\n<p>Ordering a pizza and a Pepsi from Pizza Hut is easy enough to do. I\ndeeply associate these specific foods/drinks with the holiday for many\nreasons. It was sometimes the food pairing of choice when watching the\nfilms on VHS as a kid. It's also the product placement meal inside of\n<em>Back to the Future Part II</em>, though I still don't have a\nBlack &amp; Decker Food Rehydrator with which to reheat my dehydrated pizzas.\n(Just the regular delivery of hot pizzas in cardboard boxes.)</p>\n<p>Watching all three <em>Back to the Future</em> films is definitely a marathon\nand not a sprint, so it's not quite as easy as it sounds. The few years I invited\nguests over, very few guests made it all the way through\n<em>Back to the Future Part III</em>. Sometimes that's just because fewer people\nappreciate <em>Part III</em>. (In my opinion you can't watch <em>Part II</em>\nwithout watching <em>Part III</em>. That's only watching half the movie.)\nOften it's because the 21st tends to fall on a weeknight (it was a\nWednesday in 2015, it's a Thursday in 2021) and people couldn't stay\nup that late and it was tough to start earlier.</p>\n<p>Thinking about &quot;The Future&quot; is the part that's supposed to be hard.</p>\n<p>The movies spark some obvious conversations, especially now that we\nare in positive annual celebrations. Obviously we didn't get\nhoverboards nor hover car conversions (though as\n<em>Back to the Future Part II</em> is quick to point out such things don't solve traffic, they\njust make it worse). But there's subtle things it got quite right like\nthe cyclical return of '80s nostalgia in 2015 and interesting\npredictions about smartphone tech though it generally got the form\nfactor wrong (glasses and visors rather than hand tablets). Compared\nto the so called &quot;curse&quot; of the film <em>2001</em>,\n<em>Back to the Future Part II</em> did a much better job picking brands that remained (mostly)\nrelevant/existing into the real 2015. Black &amp; Decker still existed in\n2015 though much less prominent in American life with the\ndismantlement of Sears. It was also rebranded Black+Decker in 2014,\nso the ampersand on the Rehydrator would have been an interesting throwback\nor an older model than 2015. (Because ampersands are too polite/old fashioned\nfor appliances or something? I don't understand rebrands sometimes.)\nJVC was sold by Panasonic and merged with Kenwood in 2008 and the combined company\nJVCKenwood moved to focusing on the Kenwood brand name in America and\nit was relatively unlikely to buy JVC branded stuff in 2015 other than\nfor nostalgia, mostly. (Possibly explained by Marty working for a\nJapanese conglomerate.)</p>\n<p>But something that the Back to the Future movies do particularly well,\nI think, in the &quot;got subtle things quite right&quot; department is the\nrespect for what today we often refer to as &quot;Gibson's Law&quot;, named\ntoday for sci-fi author and the ur-provocateur of the cyberpunk genre\nWilliam Gibson is the observation/realization that &quot;the future is\nalready here, it is just unevenly distributed&quot;. It's not a unique\nobservation, William Gibson helped give it a better name. In\nfurnishing and set designing the Hill Valley of the 1950s the Back to\nthe Future filmmakers realized they needed to mix in 1920s/1930s cars\nand furniture and architecture and in general span several decades of\nbrands and fashions and building designs to make the town feel real. A\nlot of that same eye was applied to the Hill Valley of 2015: most of\nthe bones are the same as 1950s (and the decrepit no one is downtown\nin the town square much any more 1980s Hill Valley) of course, but\nthey also applied changes they were already seeing in the early 1990s\nwhen they were filming and things like '80s nostalgia cafe both made\nit easier for them to dress sets, but also fit exactly the sort of\nthing that really happens within the fabric of cities. The antique\nshop selling a &quot;classic&quot; Dustbuster (another Black+Decker brand that still exists,\nthough generally surpassed by &quot;fun&quot; modern brands like &quot;Shark hand vaccuum&quot;),\nand the alleyway full of trashed Laserdiscs their own similar\nstatements.</p>\n<p>Things don't just &quot;light switch on/off&quot; into a new state: progress is\ngradual and spread thinly across early adopters, upper classes, and variable adoption\ncurves. The future is already here, it is just unevenly distributed.</p>\n<p>Contemplating Gibson's Law on Future Day I find a useful\nexercise/reminder. I appreciate that it is hard. I want to appreciate\nall the little and big things we take for granted &quot;living in\n<em>this</em> future&quot; and all the little and big things we will likely take for\ngranted tomorrow in whatever that future may bring. (Gibson's Law\nwould remind us we probably even know what that is/will be: it's\nlikely already out there in some rich person's hands right now. What\ndo you think they are doing with it?)</p>\n<p>Gibson's Law reminds us that &quot;The Future&quot; doesn't really just shock us and\nappear all of a sudden: it's a lot more\nlike the oft-mentioned boiling frog analogy, because it just creeps up\non us all slowly over time as it's mostly already here, it just may\nnot be distributed or only thinly distributed to us just yet. (Whether\nas the boiling frogs in this analogy this is healthy, is a deep and\nlong question, and arguably a key question to a lot of cyberpunk\nwriting.)</p>\n<p>The last couple of years because of COVID we've all heard and used and\ngroaned at and then tried to reclaim and then tried to dustbin and\nthen had to pick back up the term &quot;the new normal&quot;. Whatever stage of\ngrief, acceptance, and/or disdain you find yourself with that phrase right\nnow, that phrase itself is an embodiment of the hard parts of Future\nDay. What is the new normal that has snuck and whelmed its way into our lives?\nWhat do we want it to be? What do we recognize we have no way to control\nabout it? How do we control whatwe do have access to for the better? How\ndo we look forward to it with optimism rather than fear?</p>\n<p>I made up this silly holiday because I loved the <em>Back to the Future</em>\nmovies and as an annual excuse to put my rewatch marathon on the\ncalendar, on an interesting date that's mentioned in the movie, and on\nthe sort of mid/late-Fall day (weeknight aside) where curling up with\na pizza and watching three movies back to back feels like it makes a\nlot of sense. Yet it is years especially like this one, the second\nFuture Day after March 2020 and the multiple &quot;new normals&quot;–creating\npandemic that the hard challenge I gave myself to make the personal\nholiday <em>mean something</em> seems ever more crucial, ever subtly harder,\nand I suppose a lot more meaningful.</p>\n<p>If you get a chance, maybe grab a pizza and watch <em>Back to the Future</em>\ntomorrow? At least contemplate &quot;The Future&quot; and how it is already here?</p>\n","date_published":"2021-10-20T00:00:00.000Z"}]}