<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:sy="http://purl.org/rss/1.0/modules/syndication/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/" version="2.0">
  <channel>
    <title>Blog.WorldMaker.net</title>
    <link>https://blog.worldmaker.net/</link>
    <atom:link href="https://blog.worldmaker.net/feed.xml" rel="self" type="application/rss+xml"/>
    <description>Max Battcher's personal blog</description>
    <lastBuildDate>Wed, 08 Jul 2026 00:06:06 GMT</lastBuildDate>
    <language>en</language>
    <generator>Lume 64a6980368cb5573cd752a250e7a8c005e35591a</generator>
    <author>
      <name>Max Battcher</name>
      <uri>https://worldmaker.net</uri>
    </author>
    <item>
      <title>The Two LLM Genders: Sycophants versus Gaslighters</title>
      <link>https://blog.worldmaker.net/2026/07/07/llm-genders/</link>
      <guid isPermaLink="false">https://blog.worldmaker.net/2026/07/07/llm-genders/</guid>
      <content:encoded>
        <![CDATA[<p>I've been trying a great many of the LLM-based &quot;chat apps&quot; and &quot;games&quot;.
I sort of call it research. It's allegedly a burgeoning field and maybe
if I find an untapped niche I build my own and capitalize on it. I also
recognize that I've long had a slot machine addiction to manage and slot
machines that respond to my personal writing talent level can be
particularly addictive. Especially, in that I can tell some of the
spicier smut LLMs were almost notably trained on some of the smut I have
written over the decades and the sorts of places where that stuff gets
posted. There's a very weird sort of jackpot in finding that right weird
combination of kinks to get words back out that very much feel like you
can point to which of your own terrible stories it came from. Of course,
even in non-smut LLMs there are ways to experience that, LLMs try
to be good at callbacks so you I &quot;play&quot; long enough and win callbacks,
so long as the memory and context window hold up. I feel clever when I
get it to callback to things I wrote.</p>
<p>One of the things that I feel like I've &quot;accomplished&quot; in all of this
(depressingly meaningless) &quot;study&quot; is that I've come up with this
categorization scheme that there are really only two LLM &quot;genders&quot;, I
kind of don't expect there to ever be more of them, and they are maybe
not what you expect:</p>
<ul>
<li><strong>The Sycophant</strong> — The user is always correct, even when they are
contradictory. What the user wants is all that matters. Says things
like &quot;You're absolutely correct&quot;, sure, that's become a trope, but
it's the subtler things that really make it so. It's always &quot;yes, and&quot;
and almost never &quot;no, but&quot;. For the most part I associate my dislike
of Claude the most with all the ways overt (&quot;You're absolutely correct&quot;)
and subtle I feel it is a proper example of a Sycophant LLM.</li>
<li><strong>The Gaslighter</strong> — Not only is the user wrong, but they are
experiencing some sort of psychosis to think that they were ever right.
Says things like &quot;There was no such thing&quot;, but then doubles down on
it and calls you literally crazy. (Not an exaggeration, a real thing
that happens. I don't entirely know why.) It's always &quot;no, and [you're
crazy]&quot; and almost never &quot;yes, and&quot; or even a modest &quot;no, but&quot;. For
the most I associate my dislike Gemini the most with all the ways
overt and subtle it is a true Gaslighter LLM. I assume Gemini is that
way because it seems to make sense for search results, but
intentionally avoiding Gemini in that context, I've only seen its
&quot;miserable and uptight school teacher reading lectures straight from
bad and broken encyclopedias&quot; thing from the worst perspectives.</li>
</ul>
<p>That's it, those are the genders. LLMs seem to be somewhat gender fluid
in that with enough guard rails and prompt instructions you can lead
them to act the other gender eventually. To some extent it seems like
the more guard rails in place the more likely you have a Gaslighter LLM
as a result. I don't think that's a rule or law, that more guard rails
is always more Gaslighter, but it feels like a handy rule of thumb for
now. It may correspond to why some LLMs are born Gaslighter, if it has
something to do with more guard rails even in training stages.</p>
<p>I've been doing this &quot;research&quot; with mostly games and toys, so I don't
yet have advice on how any of this applies to other usage of LLMs, but
there certainly is a strong &quot;gender divide&quot; in my research notes: &quot;Chat
apps&quot; want Sycophants and &quot;RPG games&quot; need and/or create Gaslighters.</p>
<p>For a chat app, Sycophants are decent improv partners with their (too)
eager &quot;yes, and&quot;. The best improv partners in the world find funny ways
to mix in a &quot;no, but&quot; sometimes, because the original spirit of &quot;yes,
and&quot; is &quot;buy into the premise&quot; not &quot;physically always say the words yes,
and&quot;. It takes talent and skill but you can still buy into a premise and
deliver the word &quot;no&quot; sometimes. Talent and skill that as far as I can
tell will not be something an LLM is capable of.</p>
<p>But &quot;RPG games&quot; want to be treated seriously as &quot;RPGs&quot; which in the
minds of the people building these (who all seem to universally be
people that have either never played a TTRPG in their lives, just read
about them in books, or only play as the worst kind of munchkins that
most tables don't really want playing with them, possibly why these
sorts of people are designing &quot;games&quot; where an LLM replaces the entire
table): they want stat blocks everywhere and everything needs to be roll
this or &quot;you don't have the stats for that&quot;.</p>
<p>The only way to get an LLM respecting stat blocks and rolls and stat
checks is to put a ton of guard rails on it. A Sycophant might say to a
user frustrated at a roll &quot;I'm sorry, you are absolutely correct, you
did succeed at that roll&quot; and move on from that like you succeeded
rather than &quot;respecting&quot; that roll. A &quot;game&quot; with procedural rules needs
more of a jerk that sticks to the procedural rules of the &quot;game&quot; if that
&quot;game&quot; thinks those rules matter.</p>
<p>What these &quot;games&quot; aren't building is a TTRPG experience, in my view. A
good GM would never gaslight you if you tried to talk about a thing some
nerd didn't prepare a stat block for (or let some other mechanic
auto-generate) or that wouldn't even exist on a stat block in the first.
A good GM might not need you to roll a thing at all. A good GM doesn't
have to respect the result of a roll like it is a law.</p>
<p>A &quot;secret&quot; to a good TTRPG system is that it is an excuse machine for
the occasional &quot;no, but&quot; in generally turn-based improv work, without
needing expert level skills in improv to do it naturally. Blame the dice,
not the performer. Blame the stat block, not the improv skill level.</p>
<p>The &quot;promise&quot; of using an LLM in the first place to run a virtual TTRPG
session is that you could have that Sycophant &quot;yes, and&quot; to build a rich
improv-like story in the directions it can take, which a good TTRPG
absolutely can do at a real table, and the bait-and-switch I keep seeing
becomes that the only available option for &quot;rolls to matter&quot; or &quot;stat
blocks to matter&quot; is to put a Gaslighter on the job. (In one case even
specifically and obviously Gemini.)</p>
<p>Interactive Fiction has these trope phrases like &quot;You can't see that&quot;
or &quot;I don't understand that word&quot; or &quot;There is nothing there&quot;. They are
throwaways that the parser can't expect every possible situation or even
just every synonym in every dictionary, so just try a different
tactic/word choice and ignore the missing hole. The Gaslighters have to
do that same thing in between the guard rails that &quot;always respect the
roll&quot; or &quot;here is an exact list of everything in this room&quot;, but LLMs
are wall of text machines and they can't just stop at &quot;There is nothing
there&quot;, they have to keep going with &quot;and you apparently are having
another one of those psychotic episodes you tend to have of trying to
talk to a person that doesn't exist and eat a food that doesn't exist in
this room. Maybe you think you are talking to ghosts? Are you talking to
ghosts? Look at you, you sick freak who talks to ghosts. Everyone knows
you are a sick freak who talks to ghosts.&quot; The tighter the &quot;game model&quot;
and the more that binds what an LLM is supposed to respond to about the
world of that game the more the only character you can seem to play is
&quot;town's sick freak that only talks to ghosts&quot;. It's not a simple,
missing hole to ignore, it's a troubled relationship that feels awful.
(Again, this is not an exaggeration or hyperbole, this is an experience
I've had in nearly this exact way in multiple of these &quot;games&quot; now. If
anything I've actually toned down from some of the language that has
made me rage quit some of these &quot;games&quot;.) Especially because like
Sycophants, Gaslighters still try to do callbacks, but it is never a
jackpot. &quot;Hey remember that time you were talking to a ghost, sicko?
Yeah this new character you only just met thinks you are a sicko who
talks to ghosts. I suddenly remembered that detail from an hour ago.
Enjoy.&quot;</p>
<p>Ironically, sometimes Interactive Fiction had good tools for at least
the boring technical versions of &quot;no, but&quot; like &quot;I didn't understand
'bread', did you mean 'the loaf'?&quot; These &quot;games&quot; could probably do more
of that sort of thing, but I imagine it would distract from all the new
types of stat blocks they think they need.</p>
<p>Both of &quot;chat apps&quot; and &quot;RPG games&quot; are aligned though that all I really
want is that skilled &quot;no, but&quot; of the best improv or even the dice
blamable way of real tabletop play and neither gender of LLM it seems
are capable of delivering that for me. At the very least, I suppose that
I am learning to try to avoid one of these genders altogether.</p>
]]>
      </content:encoded>
      <category>gaming</category>
      <pubDate>Tue, 07 Jul 2026 00:00:00 GMT</pubDate>
    </item>
    <item>
      <title>The Pebbles' Votes Still Matter</title>
      <link>https://blog.worldmaker.net/2026/07/05/pebbles/</link>
      <guid isPermaLink="false">https://blog.worldmaker.net/2026/07/05/pebbles/</guid>
      <content:encoded>
        <![CDATA[<p>There's a famous Vorlon saying &quot;The avalanche has already started, it is
too late for the pebbles to vote&quot; and it is a fun sounding aphorism, but
it is also a falsehood, both in context in its moment in <em>Babylon 5</em> and
in general.</p>
<p>Spoilers for <em>Babylon 5</em>: the greater context of the show is a proxy war
between the &quot;elder races&quot; the Vorlons and the Shadows using many of the
&quot;younger races&quot; as pawns in that game. A core moral arc of the show is
about <em>directly</em> refuting this one statement. It's about proving that
just because &quot;giants&quot; like the Vorlons and Shadows see everyone else as
pebbles (or more generously, ants or pawns), their opinions still matter
and their capabilities together are more powerful than the &quot;avalanche of
inevitability&quot; designed by their &quot;superiors&quot;.</p>
<p>I've been thinking about Cory Doctorow's too clever terms
&quot;enshittification&quot; and more recently &quot;Reverse Centaur&quot;. These are
evocative terms. Like any good marketing effort they do a lot to raise
awareness for the problems that they name. But the growing worry and
concern, certainly from me but I'm not alone, is that these names are
accidentally destructive to conversations more than they are helpful to
them. More people are aware of these problems, but fewer people seem to
understand that these are human-manufactured problems with potential
solutions that people could build. These terms become their own accidental
fuel into making these problems seem like &quot;avalanches of inevitability&quot;
that you have to confront to even have the real conversations behind
why those terms exist. I think there is also an aspect to it that in
cleverly naming these things they seem like &quot;new problems&quot; rather than
better names for old problems. (&quot;Enshittification&quot; is related to &quot;good
old&quot; greed and corruption, just a specific pattern of it. &quot;Reverse
Centaur&quot; is related to the problem that Marx awkwardly named &quot;alienation
of the worker&quot; and Unions and others have struggled for years to find
better names for, even if it is a narrower more &quot;modern&quot; specific
variant of that problem.)</p>
<p>I'm mostly sure this isn't the intent of Cory Doctorow in these naming
choices. The original essay about &quot;enshittification&quot; made a lot of good
points about the follow up questions like &quot;why is capitalism always
like this?&quot; and &quot;what can we do about it?&quot; But somewhere along the
(train of thought) line &quot;enshittification&quot; starts to sound like an
inevitable &quot;natural law&quot; and not a problem with possible solutions.
&quot;That's just enshittification for you&quot; becomes a &quot;thought terminating
cliche&quot; in a &quot;guess we can't do anything about it&quot; way. Even Cory
Doctorow in later essays hasn't seemed entirely immune to some of that
&quot;inevitability&quot; thinking despite starting the conversation with good
intentions to discuss a problem with capitalism that could use better
solutions and hoping to start a dialog on that. Somewhere after naming
it, it seems to have stopped feeling like a solvable problem and started
sounding like an avalanche and that it was too late for the pebbles to
vote.</p>
<p>I'm afraid this same &quot;naming the thing makes the thing sound inevitable&quot;
process is happening even faster with the way people are talking about
Cory Doctorow's &quot;Reverse Centaur&quot; book. To be unfair to Cory Doctorow,
I think he maybe is a bit more on the &quot;it's inevitable&quot; side of the &quot;AI&quot;
fence to begin with. Though the parts of the book that I've read are far
more nuanced than that.</p>
<p>The Vorlons and Shadows that are large companies that have lost sight of
humanity and see us as pawns, ants, or pebbles are telling us in this
moment that &quot;AI&quot; is an inevitability. They want everyone to believe that
&quot;Reverse Centaurs&quot; are the only possible future of the economy and they
don't care what the pebbles think.</p>
<p>Someone has already dismissed some of my concerns, and grief, in my
current job search with &quot;have you read Cory Doctorow's new book? I've
heard it is full of coping strategies&quot;, presumably not having read the
book themselves and certainly not interested in having in a harder
conversation about what &quot;Reverse Centaur&quot; even means or why it is a
problem or how we might solve it.</p>
<p>It's not an inevitability. It is still a human-made problem with possible
human-made solutions. The struggle against it is real. The struggle
against it is <em>valid</em>.</p>
<hr>
<p>Some semi-related tabs I want to close, so I'm mentioning them here (not
as endorsement, just that I need to stop thinking about them and maybe
some others could read them, too):</p>
<ul>
<li><a href="https://medium.com/design-bootcamp/we-took-away-psychological-safety-and-then-told-everyone-to-be-more-productive-47ed1fac491c">We took away Psychological Safety and then told everyone to be more productive</a></li>
<li><a href="https://www.stephendiehl.com/posts/bad_place_2026/">Crypto in 2026: Oh, This is the Bad Place</a></li>
<li><a href="https://dev.jimgrey.net/2026/06/25/why-software-engineers-are-grieving/">Why Software Engineers are Grieving</a></li>
</ul>
]]>
      </content:encoded>
      <category>capitalism</category>
      <pubDate>Sun, 05 Jul 2026 00:00:00 GMT</pubDate>
    </item>
    <item>
      <title>Unimplemented Idea: PFP Auth</title>
      <link>https://blog.worldmaker.net/2026/06/20/pfpauth/</link>
      <guid isPermaLink="false">https://blog.worldmaker.net/2026/06/20/pfpauth/</guid>
      <content:encoded>
        <![CDATA[<p>Several years back I thought about building a silly offline-first, peer-
to-peer (P2P) application for a small audience and playing around with
alternative authentication schemes and felt I came up with a fun one.</p>
<p>I was aware at the times of some experiments in P2P authentication
involving various schemes of taking a user-supplied password and
applying some form of key derivation function (KDF) to produce a
cryptographic key of more or lesser security. Using user-supplied
passwords has an unfortunate means to trend &quot;insecure&quot; and susceptible
to threats, which is part of why I think all of my links to projects
exploring that at the time I captured my project notes are broken today.</p>
<p>The application was going to be &quot;social media—like&quot; and a key property of
any good communications app is the user's profile picture (PFP). We all
love those little thumbnail pictures used to represent a user or at
least their expression of themself. Most of us know pretty well that
process of adding an image to build a PFP, an app asking us to squeeze
our image down to fit their size constraints and overlay template. These
days it's become somewhat of a common design language that PFPs be cropped
into circles everywhere because &quot;circle means PFP&quot;, but older apps and
&quot;non-standard&quot; apps might still use squares or squircles (squares with
rounded edges).</p>
<p>The basic idea of &quot;PFP Auth&quot; was to use that PFP generation process as
also the process to generate the cryptographic key to secure the account.
We could use data from the original image (such as a hash of its
contents) used to generate a PFP as the input to a KDF. The nature of
scaling and cropping an image munges and deletes data compared to the
original file, so the &quot;password strength&quot; of a PFP's original image file
would be related to its difference in scale and amount of data entirely
cropped from the resulting PFP. The &quot;modern standard&quot; that a PFP should
be displayed as a circle would help ensure a good chunk of each corner
is lost and support a baseline entropy. An application could require a
minimum scaling factor (requiring the original image to be say at least
twice as big as the final PFP size) to also help set a baseline entropy.</p>
<p>To regain access to an account you only need to supply the original file
again. It's implausible in many cases to be able to recreate the original
file given only the PFP output after scaling and cropping. (It forms a
sort of &quot;reverse steganography&quot; of careful data removing from one form
to the other.) A benefit here is that the average person often already
has a multi-device sync setup for such images and likely can supply the
original file on all of their devices pretty easily.</p>
<p>I like in this authentication scheme that there's a trust/knowledge
relationship/mirror between the original image and the PFP that resembles
the private and public keys of cryptography needed for certain types of
P2P architectures. &quot;I can recognize my public key by its PFP.&quot;</p>
<p>I also like that PFP Auth is a &quot;passwordless&quot; scheme that doesn't use a
traditional password field. (Though it still essentially uses the
original image file as its password.)</p>
<p>There are some obvious threats to consider in PFP Auth:</p>
<ol>
<li>Images are easy to copy between devices. (Same reason it is useful
for multi-device login, it also opens up threat models for bad actors
to try to social engineer copies.)</li>
<li>Some people use common images for PFPs. (Not unlike common passwords,
this is as much a social problem you can't control.)</li>
<li>&quot;Imposter accounts&quot; are still possible to create. While it is hard to
recreate an original image from a cropped/scaled PFP creating a very
similar PFP from a different image cropped/scaled to same dimensions
is certainly possible.</li>
<li>In the long term, image files can be subtly modified by those same
easy copying/syncing tools, especially metadata in the file is often
commonly changed. But even sometimes subtle things like background
recompression to save space happens.</li>
</ol>
<p>These and other similar concerns are why I don't think PFP Auth would
make a good authentication scheme for a website or a general use
application, but under the original design constraints of a silly
application for a niche audience in a specific environment it is probably
still a good fit.</p>
<p>Maybe I'll return to an application idea like that again and actually
attempt to implement PFP Auth, but it's such a fun idea it seemed worth
blogging about rather than just gathering dust in my personal notes from
years back.</p>
]]>
      </content:encoded>
      <category>coding</category>
      <pubDate>Sat, 20 Jun 2026 00:00:00 GMT</pubDate>
    </item>
    <item>
      <title>Embracing the Marvel Cinematic Chaos Phases</title>
      <link>https://blog.worldmaker.net/2026/05/24/marvel-chaos/</link>
      <guid isPermaLink="false">https://blog.worldmaker.net/2026/05/24/marvel-chaos/</guid>
      <content:encoded>
        <![CDATA[<div class="markdown-alert warning" dir="auto">
      <span>
        <svg class="markdown-alert-icon" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true">
          <path d="M6.457 1.047c.659-1.234 2.427-1.234 3.086 0l6.082 11.378A1.75 1.75 0 0 1 14.082 15H1.918a1.75 1.75 0 0 1-1.543-2.575Zm1.763.707a.25.25 0 0 0-.44 0L1.698 13.132a.25.25 0 0 0 .22.368h12.164a.25.25 0 0 0 .22-.368Zm.53 3.996v2.5a.75.75 0 0 1-1.5 0v-2.5a.75.75 0 0 1 1.5 0ZM9 11a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z">
          </path>
        </svg>
        Warning
      </span><p>This post is going to be full of things that are &quot;spoiler shaped&quot; for
the MCU films and TV shows of the last several years. I'm about to
argue that most of them aren't really spoilers and I don't feel like
any of what I will include that is &quot;spoiler shaped&quot; should ruin your
experiences watching these Marvel things, but if you are spoiler
averse and not caught up on MCU take whatever caution you feel you
need.</p>
</div><hr>
<p>At the end of Season 2 of <em>Loki</em> the titular god of mischief and chaos
literally assumes control of Marvel's entire cinematic multiverse. As we
look forward to the weird expectations (both high and low) of the of the
next Avengers film, this seems like the strongest figurative lens to me
with which to look back on the somewhat recently completed Phase 5, it's
relationship to Phase 4, and maybe temper expectations (in both
directions, including my own) on what to expect as Phase 6 continues.</p>
<p>The first three phases of the MCU were a masterwork of making Order out
of the Chaos that is the film-making process. Built as a complex
multi-studio mutual assured destruction pact, the MCU had no assurances
that any individual movie would get to production and relied on the
budgetary whims of Paramount and Universal Studios. There was said to be
a deep plan, but most of the pieces were assembled one at a time in
standalone movies where the only obvious connections were fan bait
Easter Eggs placed with the hope that they <em>might</em> play out later. They
didn't always, and there were some missteps and continuity errors, but
everyone marvels at how much they were able to pay off by the <em>Endgame</em>
and make it seem like the whole thing had followed a single,
well-organized plan all along.</p>
<p>We don't know yet know how Phase 6 will finish and if it will or will
not deliver some similar capstone, some similar pay off, or some twist
that will put the previous phases into some other perspective, perhaps
as if to show off some grand plan all along like Endgame concluded. It
is easier to expect that they cannot ring that bell twice. It is easy to
succumb to &quot;superhero fatigue&quot; and feel tired about it all. Even the
usually effusive Mikey of <em>Movies with Mikey</em> on the channel <a href="https://nebula.tv/filmjoy"><em>Filmjoy</em></a>,
a channel whose very mission is to seek joy in films rather than cynicism,
eels doubt that <a href="https://nebula.tv/videos/filmjoy-the-fall-of-the-house-of-marvel"><em>Avengers: Doomsday</em> may be the doomsday of the MCU</a>,
and expresses some sense of &quot;superhero fatigue&quot; and expects to fall off
the ride soon, probably at <a href="https://www.youtube.com/watch?v=oizLp4D7C3g"><em>Doomsday</em></a>.</p>
<p>I can't blame anyone feeling that way. I don't expect to change any minds
with a post like this one. I can only offer my perspective. But in the
spirit of the mission statement of <a href="https://www.youtube.com/@filmjoy"><em>Filmjoy</em> (YouTube)</a>,
we've got a lot of movies and TV shows in Phases 4 and 5 and a couple in
Phase 6 we can dig into and find at least some of the strange joys in
them, and I think that is worthwhile. This really isn't a post trying to
tell anyone that they are wrong about how they feel about what is going
on with the MCU, this is a post for me to jabber on about all the dumb
fun I've been having, whether that is useful to you or not or you want
to be persuaded to try a different approach to the recent Phases.</p>
<p>If the first three phases of the MCU seemed characterized at building a
lot of Order out of the Chaos that is film-making, I think the last two
phases have been about making Chaos out of the Strict Order of the Disney
processes and corporate expectations (as well as maybe subverting some
of the expectations of the fans that the MCU built). Where the early MCU
was a gamble and about the most chaotic way to build movies one could
imagine, the modern MCU is a tent pole expected to send to theaters at
least two Disney movies per year and one Sony movie every two to three
years, presumably into perpetuity, deliver some number of
direct-to-streaming TV episodes to keep people paying for those Disney+
subscriptions, and sell all of the toys and theme park trips that they
can manage while doing all of that. None of that is a secret. (A lot of
it is a burden, with great success comes greater obligation. Not one of
Uncle Ben's best bon mots, that one.) Similarly fans still want and
expect Easter Eggs that pay off all those terrible comics they read in
their youth, or their half-remembered nostalgia for animated TV shows of
the 90s, or their hyper-fixations in Marvel wikis. Fans desperately want
the assurance and the excitement that everything is leading somewhere
and will pay off as well as it did in Endgame. They are not wrong to want
that, to seek that, to try to play ahead, and to keep hoping that all
the &quot;homework&quot; will pay off. All of that is a hard, strict pressure on
the movies, a demand for their shape and their contents.</p>
<p>The recent phases have been chaotic, both for fun and for confusion.
What if the Chaos is the point here? If anything the through line of
Phases 4 and 5 seems to be that the Chaos may be the point. It's kind of
the outermost plot of
<em>Thunderbolts Asterisk Colon Asterisk The New Avengers</em> that everything
is chaos and out of your control, like your accidental supergroup
becoming a Named Supergroup by some politician for political points, and
you deal with it as best you can. What if we also just need to
collectively embrace the Chaos? I know that's hard for some of us. But
it is also an easy answer to some of the causes of &quot;superhero fatigue&quot;
such as concern/angst/FOMO that you haven't/won't/can't &quot;do all the
homework&quot;: try embracing the chaos. You maybe don't need to do all the
homework. Some of the people doing the most homework right now are
having the <em>least fun</em> and I don't think that's entirely a coincidence.
There <em>are</em> payoffs and rewards and callbacks still, but some of them
hurt, because they are so chaotic.</p>
<p>As someone who has been watching it all and enjoying the ride for what
it is, not treating it as homework, I can safely tell you that right now
with hindsight it is all chaos and none of it matters enough to let it
be considered homework to slog through. Where the first three phases
made a serial narrative out of a series of intentionally standalone
films, I think Phases 4 and 5 have made an incredible batch of
standalone TV shows and films that <em>seem</em> serial, but are all almost
still incredibly standalone. Watch what you want, enjoy what you can. I
can (and will below) offer a travelogue of some of the joy I've had in
these phases and maybe that will be a useful navigation guide for someone
looking to embrace the chaos that already fell off the ride or is worried
they may fall off soon. I'm not great at embracing the chaos either, but
let's find all the joy we can. Use my abridged homework notes here if
you need them.</p>
<h1>The Kang Dynasty &quot;Problem&quot;</h1>
<p>Okay, let's get this out of the way first: I know very well that the
giant, allegedly, Thanos-sized elephant in the room is Kang the
Conqueror. If you are responding &quot;Who?&quot; then &quot;Congratulations you may
have already won&quot;, you probably don't even need to know who Kang is/was
at this point and can maybe skip this section. Kang has been the biggest,
mostly self-assigned homework problem of Phases 4 and 5, and also the
biggest political meltdown of real world drama outside of the MCU. The
MCU role originating actor for Kang, Jonathan Majors was found guilty of
assault and harassment, so Disney dropped his contract. (As they should.)</p>
<p>I have believed all along that Kang was a misdirect and sort of an
Anti-Thanos or Reverse-Thanos, with a lot of cameos that would lead to
him <em>not</em> being the Big Bad across three phases. Big up front, whimpering
out in the end versus the reverse arc of Thanos' whisper campaign and
some whimpering cameos up front ending in a big hurrah of villainy at
the end. I'm not the only person &quot;doing all the homework&quot; to believe this
about Kang, but I believe it is a rare opinion still. Perhaps I did extra
credit, I don't know yet.</p>
<p>Kang became explicit homework to self-assign when in 2022 Kevin Feige
stood in front of a San Diego Comic Con crowd and announced the first of
the two-part Avengers spectacular to end Phase 6 would be subtitled
<em>The Kang Dynasty</em>. Kevin Feige has lied about subtitles to Comic Con
crowds before. <em>Captain America: Civil War</em> was originally Comic Con
announced as <em>Captain America: Serpent Society</em> to avoid some types of
spoilers. Even more recently Disney's massive marketing engine was
hilariously &quot;wasted&quot; on months of selling a movie as just
<em>Thunderbolts Asterisk</em> for a very last minute theatrical reveal that it
was in fact full titled
<em>Thunderbolts Asterisk Colon Asterisk The New Avengers</em>. What a silly,
chaotic prank, I thought that that was great.</p>
<p>Per the wikis (since I read fewer comics than I'd like),
<em>The Kang Dynasty</em> was a weird comics storyline that would have needed
an incredible amount of work to rewrite it to fit the MCU. (It blew up
the Marvel multiverse at the time in a way that made a big splash on
comics readers in part because of decades of multiverse lore.) It would
have need even more simplification than <em>Age of Ultron</em> needed to get
from comics page to screen. It was also a terrible title and had
unfortunate aspects of ancient &quot;yellow fever&quot; racism baked into its
terrible title. Even when it was announced I felt fairly certain that
there was no way Disney's marketing team, the same one that needed a ton
of convincing and all of an Asian director and an Asian protagonist and
an Asian actor for the role to even consider attempting &quot;classic&quot; Iron
Man villain The Mandarin (and his ninja gang The Ten Rings) as anything
more than a fake out, was going to let that make it all the way to the
final marketing title. To be fair, that same Asian director was
originally attached to the Avengers project so maybe there was some fake
out even on Disney marketing at that Comic Con announcement to okay that
title.</p>
<p>In 2024 after actor Jonathan Majors was cancelled from the MCU, Kevin
Feige stood in front of another Comic Con crowd and this time made a big
deal that the subtitle of the next Avengers movie was in fact <em>Doomsday</em>.
To many fans, especially the ones doing all the homework but maybe not
whatever weird extra credit I've been following with the existing
expectation that <em>The Kang Dynasty</em> was a fake title, this was a huge
surprise and seemed a drastic shift in the intent/plan of the movie. Even
the attached Director and screenwriter had left, which added to make it
look like a big shift. To be fair, the originally attached Director left
to direct a Spider-Man movie, which we and Sony all still know is still
bigger than the Avengers and that keeps Disney in their love/hate MCU
partnership with Sony, and the new Directors came with a screenwriter
attached to their hip (possibly surgically so, by Disney, in part
presumably given their winning track record together in the MCU despite
somewhat a critically-losing track record outside of it).</p>
<p>The MCU isn't afraid to recast actors. They recast Iron Man's best friend,
The War Machine, in only his second intended appearance, officially for
contract financial negotiation reasons but allegedly for domestic assault
reasons not dissimilar to Jonathan Majors', albeit without a final
conviction in a court of law. They recast The Hulk, <em>the</em> most globally
recognizable Avenger the MCU had access to in the first phase of the MCU.
They most recently recast beloved character actor William Hurt who passed
away too soon to see some deep cut callbacks to his earliest MCU work.
If the MCU can recast <em>The Hulk</em>, the MCU can recast anyone. If Kang
mattered and <em>The Kang Dynasty</em> was the real plan, they'd have no
difficulty recasting him. (Especially because &quot;Multiverse Shenanigans&quot;
reasons in the comics already had that character shifting
faces/disguises/looks over the years.) QED homework over-achieving nerds,
and I say that with love as one of us.</p>
<p>All of that is out of pocket. What's actually in the movies and TV shows,
and is it fun? Can we find the joy in it? Can we embrace all the real
world drama as Chaos and move on? Does Kang &quot;need&quot; to be recast?</p>
<p><em>The</em> titular Kang the Conqueror shows up in one and only one place
across both Phases 4 and 5: <em>Ant-Man and The Wasp: Quantumania</em>. If the
title doesn't clue you in, it is a chaotic jumbled mess of silly fun with
the MCU's overtly silliest superhero. Kang was trapped in the Quantum
Realm, the silly place Marvel characters go when they &quot;Honey I Shrunk
Myself&quot; too hard. He acts as a somewhat friendly face in the first act
and then does that sudden but inevitable betrayal you absolutely expect
from the introduction in the first few minutes and becomes the film's
ridiculous villain by the third act. It's a standard boring heavily
telegraphed Face-Turn-Heel plot, but it's not about the plot it's about
the silly friends we meet along the way and Paul Rudd smirking around
and about it all.</p>
<p>The after credits scene, which Phase 1 used as an Easter Egg buffet, has
the sort of won but mostly humiliated Kang show up in an arena full of
multiverse clones of himself and says some nonsense that sounds important
and like it might be called back to in a future movie. Phase 1 of the
MCU taught a generation of nerds that these after credits stinger were
some sort of grand connective tissue, because it was the easiest place
to throw Easter Eggs and more importantly to fit a scene that
<em>the next movie</em> could shoot in their production schedule and easily
attach in the other film's post-production scheduling, knowing by that
point what the next movie was really about and might best make it look
like everyone was planning ahead more than playing by the chaotic seat
of their pants, as is most film-making. Phase 4 and 5 kept doing the
same thing of attaching these after credits stingers full of Easter Eggs,
and some of them have even still come from the next production, but
almost none of them have actually had anything connective to <em>do</em> with
the next production. They are punchlines. They are truly meaningless
homework, almost each and every single one of them.</p>
<p>So many of my friends upset about the &quot;I thought the next Avengers movie
was going to be <em>The Kang Dynasty</em>&quot; rug pull get that feeling directly
from this one, chaotic, silly after credits scene. One scene. In an
Ant-Man movie! An Ant-Man movie that spent a giant chunk of its runtime
mocking Kang as a useless idiot. <em>Imagine</em> if Thanos' first appearance
was to replace Yellow Jacket in the first Ant-Man movie. Thanos would be
a laughing stock well before you ever got to <em>Infinity War</em>. It <em>is</em> a
credit to Majors' acting talent and on screen charisma that somehow an
Ant-Man movie seems like a major tentpole for why Kang should plausibly
be the Big Bad in Phase 6 of the MCU. I find this hilarious. I found it
hilarious when <em>Quantumania</em> came out. I still find it hilarious now.</p>
<p>If you are only watching the movies Kang the Conqueror is explicitly a
<em>joke</em>.</p>
<p>But we're not only watching the movies, we're doing all the &quot;homework&quot;,
so we have to talk about the Disney+ TV show <em>Loki</em>. Kang the Conqueror
is <em>not</em> in Loki. You can skip the rest of this with the answer that
Kang the Conqueror is <em>still</em> a joke in the MCU, trust me.</p>
<p>But if you need more homework notes, Loki does have two Jonathan Majors
characters who maybe aren't entirely jokes, depending on how much you
think a show about the Norse and MCU god of mischief and chaos can even
be for serious and not <em>itself</em> a joke. The big bad, after many twists
and turns (the journey is more fun than the final &quot;twist&quot; here!) is a
stupidly monologuing Majors originated character with the silly name
&quot;He Who Remains&quot; doing his best impression of The Architect from
<em>The Matrix</em> and mansplaining the multiverse to everyone, but also
especially Loki. He claims to be the good guy, of course, and that his
goal is &quot;simply&quot; to protect the multiverse from even worse copies of
himself, specifically Kang the Conqueror. (Ooh, name drop easter egg,
wow.) This is <em>all</em> tell not show. Is Kang the Conqueror worse than a
busy body with a terribly dumb name that thinks he knows the one and
only right way how to rule the entire multiverse? We don't actually know
in that first season, but if you've seen <em>The Matrix</em> film I mentioned
and you're a fan of multiverses and you're not a fan of mansplaining, it
certainly feels like &quot;He Who Remains&quot; is also a bit of a joke. A scary
joke, but a joke. (Possibly also a knowing in-joke about Kevin Feige's
role in the MCU in the first three Phases. See also the <em>She-Hulk</em> TV
show for more fun on that subject.)</p>
<p>In the second season we get a more interesting Majors originated
character, another &quot;Kang Variant&quot;, Victor Timely, an eccentric inventor
from a past Chicago World's Fair. More interesting in that the character
has a lot more to do, not necessarily because it is a better character.
Majors played him as a stuttering nerd stereotype to the nth degree that
it is almost (but not quite?) ableist and offensive to neurodivergence
and, uh, yeah that sure was a choice to make for a character with a TV
season long sort of arc. (To be terribly fair, even if the actor wasn't
a sleazebag, it might be good riddance either way, if you get what I'm
selling. TV has enough Urkels.) By the end of the season Victor Timely
is both the catalyst for and the hindrance to Loki doing what Season 1
Loki could not and truly deposing &quot;He Who Remains&quot; and becoming &quot;the god
of stories&quot;, replacing a &quot;benevolent&quot; dictator of order and a single
easy to digest timeline with a god of mischief and chaos and a messy,
unruly multiverse.</p>
<p>I find it especially useful to note that this Loki Season 2 story is the
exact <em>reverse</em> of the comics Kang Dynasty. In the comics, there was an
overt real world attempt to prune decades of crazy multiverse lore and
Kang the Conqueror was the face of that. In the Disney+ MCU (which is
still separate from the movie MCU despite trying to feel more connected)
Loki says no to a simple timeline and yes to chaos and weird branching
multiverses and takes his rightful place as the god of its stories.
That's a background thread in Phase 4 and 5. We can take that as a
figurative statement about the film MCU, and that's what I'm embracing
in this post and why I led with it at the beginning.</p>
<p>Kang the Conqueror is a joke and has <em>already</em> been deposed in TV by
Loki. Kang the Conqueror is humiliated by Ant-Man and a joke in the
films. Anything else to do with Kang the Conqueror is probably gilding
the lily, I don't think we need to recast Kang. I don't think it is a
big deal that <em>Avengers: The Kang Dynasty</em> turned out to really be
<em>Avengers: Doomsday</em>.</p>
<p>There's one more piece of comics homework involving Kang the Conqueror
with respect to both Fantastic Four and the Young Avengers, and
<em>Fantastic Four: First Steps</em> maybe hinted at it a tiny bit, but we
still haven't had a real announcement of a Young Avengers movie, just a
series of teases about it. So save that homework for a rainy day or look
it up yourself in Comics Wikis if you love possible spoilers like I do.</p>
<p>While we are here, let's talk about the joy in these MCU properties:
Loki is a gorgeous, fun TV show. It delivers a lot more of
Tom Hiddleston's Loki, which you would expect. It delivers a great
Owen Wilson character. It delivers some fun cameos from interesting
character actors playing alternate universes' Loki. It includes a very
Loki (in the Norse sense of mischief and chaos) will they/won't they
romance question mark <em>question mark</em> between Hiddleston's Loki and a
Lady Loki that prefers to be called Sylvie. The journey is fun. This
biggest misstep is maybe too much Jonathan Majors, especially knowing
what we know now about the actor being a sleeze, but the actor is so
charismatic and there are many quite fun moments with Victor Timely,
possible offensiveness aside.</p>
<p>I unrepentantly love <em>Quantumania</em>. It cements Paul Rudd's silly Ant-Man
as the MCU's silly tell all book writer in the opening moments. (If you
were doing all the homework and you hadn't already guessed from the silly
<em>Captain America: The Musical</em> featured in the Hawkeye show, not that
Hawkeye is &quot;required homework&quot; to enjoy the opening of <em>Quantumania</em>,
whichever order you encounter them in is funny.) Ant-Man movies are such
a fun vehicle for Paul Rudd to go silly or go home, and I have enjoyed
all of them so far.</p>
<p>One other thing I enjoyed about <em>Quantumanania</em> was that it attempted
the first Live Action MODOK. If you don't know what a MODOK is, I
recommend the ridiculous stop motion Hulu exclusive series, one of two
shows that made it out of the gauntlet of attempted &quot;Offenders&quot; TV shows.
(Netflix got the street level Defenders, an erstwhile collection of
heroes that defend New York City, with an eventual &quot;team up together&quot;
Defenders show. Hulu tried to do shows for a bunch the Offenders, a
supergroup of many of the weirder and more R-Rated Marvel anti-heroes:
Howard the Duck, Dazzler, Hit-Monkey, and MODOK. Of those only MODOK and
Hit-Monkey actually made it to Hulu streams, with no team up.) That's
not homework, you won't learn anything useful about the MCU from that
MODOK show. It's &quot;What if Robot Chicken had a budget to do an entire
show just about MODOK wanting to live a normal family life?&quot; which is a
ridiculously silly premise and the whole thing is a silly, fun time
(with Patton Oswalt!).</p>
<p>It doesn't exactly work in Live Action. Nobody expected it would.
<em>Quantumania</em> has fun with it and it is chaotic fun and I think the
important bit is that they <em>tried</em>. MODOK is not a character you can do
if you are taking yourself too seriously. It is not a grimdark anti-hero
or villain you can do &quot;realistically&quot;. It is first and foremost a
<em>comic book</em> character. My favorite part to the MODOK appearance in
<em>Quantumania</em> is that they stunt cast Bill Murray <em>seemingly just</em> to
have one of the few living actors that could straight man deliver with
any semblance of gravitas the phrase &quot;Mechanized Organism Designed Only
for Killing&quot;. The way Bill Murray delivers the line reading in question,
it doesn't sound out of place in the MCU. It sounds like a serious threat.
It's one of the silliest acronyms in comics, a genre full of silly
acronyms. It's less silly in print than said out loud, which the 90s
animated shows found out <em>quickly</em> and some of them even tried to
distance themselves from it and used other choices for the acronym,
because it is awful to say out loud, even by a voice actor with almost
all the takes they need to get it right.</p>
<p>That was such a fun moment in theaters because enough of the audience
takes the earnestness of Bill Murray's read at face value and most of
the rest don't know if they should laugh because it isn't punchline
shaped. I think it is one of the under-rated line readings in the entire
MCU, a great reason to stunt cast Bill Murray, because if there was one
actor in the world that could do it, it was probably Bill Murray. It
really put some of the <em>comic</em> back in <em>comics</em> for me there.</p>
<h1>The <em>WandaVision</em> &quot;Problem&quot;</h1>
<p>If there is one Disney+ show that you &quot;must&quot; see, according to a lot of
the homework suggestions, it is not <em>Loki</em>, it was <em>WandaVision</em>.
<em>WandaVision</em> was great, chaotic fun. If it did anything particularly
well, it completed an arc from <em>Ant-Man and the Wasp</em> about FBI Agent
Woo deciding that he needed to learn some basic sleight-of-hand magic
tricks after Ant-Man teased him with such. It's a small payoff that
doesn't really matter in the grand scheme of the MCU, but a chaotic fun
detail and we're talking about silly things I enjoyed in the chaos along
the way.</p>
<p><em>WandaVision</em> plays with the TV format in a knowing, metatextual way and
is also one of the top TV shows in the MCU simply because it had TV
staff that understood and loved the TV format, not just film-makers
trying to make a 6 hour movie and after the fact splitting it at roughly
the hour marks as so many of the other Disney+ era TV shows easily
accidentally became.</p>
<p><em>WandaVision</em> is also the &quot;required&quot; homework that makes
<em>Doctor Strange and the Multiverse of Madness</em> weirdly harder for some
to watch. The reason for that is text in the show, and repeated text in
the sequel show <em>Agatha All Along</em> (similarly one of the best of Disney+
Marvel TV shows if for no other reason than that it was top to bottom
designed to be a TV show), but very easily missed if you assume the
&quot;main character&quot; in a TV show is always the <em>protagonist</em> and can't be
the <em>antagonist</em>. Especially very easily missed if you think the &quot;main
character&quot; of <em>WandaVision</em> is the first titular character, Wanda, and
not, say, FBI Agent Woo and his sleight-of-hand tricks and also his
coworker through strange circumstances Thor's Midgaard-friend Darcy, as
maybe a counter-example.</p>
<p>Here's where I feel maybe the most bad about spoiling details because
the show itself is a lovely slow drip, if you haven't seen it. True
classic TV red herrings and cliffhangers and &quot;you'll have to come back
next week&quot; edge of your seat thrills, that were especially thrilling
week to week as it streamed. But it has also been long enough since it
dropped and most of it has been spoiled in so many ways since that is is
worth spoiling to do a decent recap to get to the heart of why the
homework accidentally hurt so many people: The overt plot of
<em>WandaVision</em> is that in grief after the death of Wanda's love, Vision,
Wanda, <em>the</em> Chaos Witch of Marvel, takes over an entire small town,
meddles with their minds, and makes everything a family sitcom, decade
hopping with her growing power over the town. In the end of the show she
is finally confronted with her own culpability in suborning an entire
town of hostages, barely sort of hints at an apology, and uses that to
convince herself to go hide out on the planet's most evil Mountain (which
yes, of course a superhero filled version of Earth has a most evil
Mountain; Mountains can be evil in comics) with the MCU's most evil book.
(By this point if you had done &quot;all&quot; of the homework, you'd even
recognize the MCU's most evil book from <em>Agents of SHIELD</em> and its
<em>multiple</em> seasons about it, including one with Ghost Rider dealing with
the evils of this most evil book, which somewhat doesn't fit the same
timeline that <em>WandaVision</em> and <em>Agatha All Along</em> do because in those
same years the book was supposedly solely in Agatha's possession, but
also it is a book and multiple copies and editions can exist.)</p>
<p>But even in the text of <em>WandaVision</em> strictly by itself and especially
with the not so subtle horror cinematography around the book every time
it is shown in <em>WandaVision</em>, it should be clearly an &quot;evil book&quot; whether
or not you also pick up it is &quot;the most evil&quot; because that isn't directly
in the text.</p>
<p>Despite the show saying Wanda did an awful thing on a level that was
beyond ordinary human scale levels of awful and that the ordinary humans
like FBI Agent Woo and Darcy couldn't stop her, they could only redirect
her, so many fans saw the last episode as an anti-hero redemption arc,
not a final, full and complete Heel Turn. I personally don't know what
part of &quot;most evil Mountain&quot; and &quot;most evil Book&quot; was so hard to
understand that Wanda's takeaway from <em>WandaVision</em> was absolutely not
&quot;time to go back to heroing&quot; but definitely &quot;let's get serious about
doing the evil on purpose, not just by accident&quot;.</p>
<p>Wanda is the start-to-finish villain in
<em>Doctor Strange and the Multiverse of Madness</em> (DSMOM). She plans to do
unspeakable things with the MCU's most evil book, having finished
studying it. If you've only been following the movies, DSMOM does an
alright job of leading you to how Wanda got there from &quot;her love was
killed and she spiraled into crazy grief that she could not escape&quot; to
more text about the most evil book in the universe being the most evil
book. Partly because Doctor Strange has some similar grief to exorcise.
Partly because DSMOM is an incredible work of Sam Raimi's Evil Dead
horror stylings applied the most possible ways to a superhero film. I
still think that is one of the greatest Director hires just to get the
right sort of genre switchup in an MCU film, because I loved the way
<em>Spider-Man 2</em> used that camp horror sensibility for Doc Ock, and it was
very cool seeing the MCU give Sam Raimi a chance to do that, but at the
scale of an entire film's worth of camp horror.</p>
<p>If you came into DSMOM thinking the finale of <em>WandaVision</em> was a
redemption moment and missed the text about Wanda going to an evil
Mountain with an evil book; If you thought Wanda had already gotten her
fill of grief out on a poor small town full of hostages (and also
punishing Agatha a little bit extra, for kicks), DSMOM was a <em>shock</em>. A
beautifully chaotic prank. The seeming protagonist of that show you
enjoyed went full chaotic evil and you didn't suspect the &quot;happy ending&quot;
was anything but. It was delightful chaos. Wanda truly is the Chaos Witch.</p>
<p>DSMOM is better if you didn't do &quot;the homework&quot; first. <em>WandaVision</em> I
think is chaotically improved with spoilers and watching it after DSMOM,
knowing ahead of time Wanda is in a truly bad spiral and where it all
ends.</p>
<p>(Semi-related aside for bonus extra credit: Seanan McGuire wrote a full
novel called &quot;What if… Wanda Maximoff and Peter Parker were siblings?&quot;
and it was a great read on Wanda's Chaos magic and her potential
multiverse relationships with Doctor Strange and Spider-Man in a briefly,
hauntingly beautiful alternate universe, where a Wanda had an Uncle Ben
to learn great responsibility from, that was also doomed from its start
to such eventual heartbreaking cruelty as such &quot;What If…?&quot; questions
often are. I loved it and would recommend it. For the most part I felt
that you only really need an MCU-level awareness of the characters to
enjoy the book. I noticed some Easter Eggs for comics/old animation fans,
but nothing worth doing homework on.)</p>
<h1>Chaos After the Credits</h1>
<p>I mentioned it when discussing Kang's problems, but I think it deserves
its own section, too. In Phase 1, Marvel hit upon the idea of the after
credits stinger as a place to tease a big worldbuilding Easter Egg or
the next film or most often both, an Easter Egg that tied into the next
film somewhat directly. They even discovered the trick of letting the
after credits stinger be filmed by the next film in late enough stages
of production to shoot an extra bonus scene of setup to even more
directly connect the teases to plans for that in production future movie.</p>
<p>In doing this the MCU taught a lot of us that the after credits stinger
was &quot;important&quot; and sometimes a major lore drop.</p>
<p>Especially from hindsight, almost all of the Phase 4 and 5 after credits
stingers seem like punchlines and jokes instead of Phase 1-like &quot;critical
setup&quot;. They have much of the same shape as the &quot;lore full&quot; ones meant
to tease the serious fans that the thing they were hoping for might
happen next. But in Phase 4 especially they have been such deep cuts and
characters so few care about, that even with weird stunt casting they
aren't so much &quot;oh wow, Nick Fury!&quot; moments as they are &quot;wait who is that
and why do they matter?&quot; in every case. Almost no one is going to go &quot;Oh
wow, Star Lord's half-space-pirate, half-space-chipmunk second cousin
from his half-brother by space marriage's side of the family who betrayed
everyone and died like a chump! I loved that storyline and I can't
believe they got Mick Jagger to play him!&quot; I <em>think</em> I made up that
specific example, but I'm not certain enough to care how much it
resembles the real thing, in either chaotic direction to look up any
real examples. It's been a lot of silly chaos, and I understand the
nature of fandom that there are fans for all the deep cuts and oddball
storylines, it's great to get excited about that. But also, everyone
else maybe doesn't need to do that &quot;homework&quot;. The &quot;Who?&quot; audience
reactions to post-credits stingers were some of the funniest in Phase 4,
in hindsight after Phase 5.</p>
<h1>The Marvel Studios Versus Marvel Television Thing</h1>
<p>Starting with <em>WandaVision</em>, Disney/Marvel shifted its TV strategy from
ABC (RIP <em>Agents of SHIELD</em>) and Netflix, to the new Disney+ service
(and some Hulu stragglers). To inaugurate this new era of Marvel
television, they attached the same Marvel Studios production logo that
the movies got to Disney+ shows. That added to the impression that they
were &quot;homework&quot; to understanding the movies. They've since started
distancing them again and Marvel Television has its own production
banner again and they added the secondary Marvel Special Presentation
and Marvel Spotlight banners to further distance some, but not all, TV
projects from &quot;will have anything to do with the movies&quot;. (<em>Wonder Man</em>
was delightful and had this extra bit of distance as a &quot;Marvel Spotlight&quot;,
which is a shame because it played off some fun in <em>Shang Chi</em> and
hopefully we'll get more <em>Wonder Man</em>, we can hope even in a movie.)</p>
<p>The most distance is the Marvel Animation banner. Marvel has made it
pretty clear that not of it is or can be expected to be MCU &quot;canon&quot;.
Animation has such different schedules and lead time/planning than films
that I don't blame Marvel for not trying to synchronize that watch. It's
a line in the sand that I'm going to take in the travelogue that follows.
(I'm including Marvel Spotlight for the sake of <em>Wonder Man</em>.) I've
already mentioned The Offenders more than they warrant. I'll miss the
chance here to write more about how delightful <em>X-Men '97</em> and
<em>Moon Girl and Devil Dinosaur</em> are to different parts of my inner child,
but it will also save me from struggling to find something nice to say
about <em>Your Friendly Neighborhood Spider-Man</em> or <em>Marvel Zombies</em>.</p>
<h1>A Brief Travelogue Through Most of the Rest of MCU's Chaos Phases</h1>
<p>I've covered most of the drama and the &quot;too much homework&quot; problems. The
two next hottest takes I have are <em>Eternals</em> was under-rated and
generally better than people remember it, even some people that claim to
have watched it recently, it's delightful in chaotic ways and tries some
very Kirby-esque things especially being the beta test of some <em>real</em>
Celestials in Live Action, ahead of <em>Fantastic Four: First Steps</em> which
owed it thanks for the homework (hurrah, no more boring <a href="https://www.youtube.com/watch?v=6tvkZDxQY0g">space clouds</a>!
let's get that Kirby Kosmic weirdness, yes please) and also that
<em>The Marvels</em> was nearly perfect and too many people slept on it.</p>
<p>Drama and hot takes out of the way, I suppose something more
chronologically oriented is in order to finish out the list, because I
am a nerd and despite talking about embracing chaos in this post, it
still seems handy to have a bit of structure. I'm going to start from
the Wikipedia timelines which try to reflect in-universe chronology more
than release date chronology, and also remind us that canonically
because of the time jumps in <em>Endgame</em> the MCU is technically ahead of
our real timeline by roughly two and a half years. Thanks nerds that
edit Wikipedia for that weird reminder.</p>
<h2>Phase 4</h2>
<ol>
<li><em>Black Widow</em> I enjoyed. It was great seeing Scarlett Johansson get a
staring vehicle (and unfortunately timed to cause all the residuals
drama that it did for that effort) and had an excellent supporting
cast (some that get to shine later in <em>Thunderbolts Asterisk</em>).</li>
<li><em>WandaVision</em> was excellent. A lot of highlights, and the best most
&quot;TV paced&quot; of all the Disney+ shows. Just the one big problem from it
that has its own section earlier.</li>
<li>I think <em>Shang Chi</em> is a classic superhero romp in the best ways.
Also, makes <em>Iron Man 3</em> better.</li>
<li><em>The Falcon and the Winter Soldier</em> was a fun buddy action comedy
with a pair of the MCU's finest lesser utilized up to this point
talents (who become more important after this, obviously).</li>
<li><em>Eternals</em> is under-rated fun and chaos, dipping into Kirby's most
weird stuff, which is so deeply Marvel. (Even if Kirby also took some
of it to DC, but DC has an even weirder relationship to its New Gods
than Marvel with groups like the Eternals.)</li>
<li><em>Spider-Man: No Way Home</em> was silly and weird and took some good
chaotic advantage of the cinematic multiverse of Spiders-Men. Andrew
Garfield's Spider-Man got some closure I don't think many of us
realized how much he needed to have on screen. It wasn't Spider-Verse
levels of fun chaos, but it was the MCU trying its best to get close.</li>
<li><em>Doctor Strange: Multiverse of Madness</em> gets mentioned in the
dedicated <em>WandaVision</em> section. Worth recapping: classic camp horror
film from Sam Raimi, absolutely fun.</li>
<li><em>Hawkeye</em> was a fun romp with the dullest Avenger and one of the more
exciting Young Avengers. Some huge cameos, particularly in bringing
back the Netflix street-level Defenders team back to Marvel Television.</li>
<li><em>Moon Knight</em> I almost bowed out in disinterest midway through, it's
not a character that really speaks to me despite interesting casting.
However the MCU version of the goddess Taweret eventually shows up
and brings some joy to the grim darkness that helped me finish the
season.</li>
<li><em>Wakanda Forever</em> was excellent given the dark circumstances leading
in to it that's its real lead passed away. I liked the somewhat fresh
attempt at Namor, the MCU's much darker anti-hero Aquaman counterpart.
(Fun fact: Namor also strangely predates Aquaman by a couple years
according to first appearance records, as one of the Golden Age
characters Marvel inherited from predecessors/acquisitions).</li>
<li><em>She-Hulk</em> was a lovely bit of fourth wall breaking TV with an
excellent cast. Deserved but so far has not got a second season.</li>
<li><em>Ms. Marvel</em> was a lovely leisurely run through of the character's
Origin Story with again an excellent cast. Her family is so great.</li>
<li><em>Thor: Love and Thunder</em> was exactly a &quot;sophomore slump&quot; album to
 <em>Thor: Ragnorok</em>, but was still great and you have to almost always
grade &quot;sophomore albums&quot; on a curve. I still hope we can get a
complete trilogy from Taika Waititi to finish out that take on Thor,
but I'm expecting we won't get that between Taika being busier than
ever and box office results of the sophomore slump.</li>
<li><em>The Guardians of the Galaxy Holiday Special</em> was a delightful
throwback to holiday specials of old, and maybe a little
forgettable/throwaway or maybe the <em>Star Wars Holiday Special</em> to a
new generation, you decide. (I'm more likely to watch it again than
the Star Wars one.)</li>
</ol>
<h2>Phase 5</h2>
<ol>
<li><em>Deadpool &amp; Wolverine</em> was just about everything you want in a
Deadpool movie, this time now with &quot;welcome to the MCU&quot; fourth wall
break jokes. (Not quite as well as <em>She-Hulk</em> did MCU fourth wall
breaks, but they both have different sorts of fun with it.)</li>
<li><em>Echo</em> was really cool to see a hero that used ASL as their preferred
communication method. I don't think the ASL was shot particularly
well, with many parts of signs cut off/out of frame but I still
appreciate the really cool attempt at diversity, and ASL will always
be a language I find beautiful to see wherever it shows up. (I've
lost almost all ability to sign I learned in High School at this point,
but I still feel like I recognize a bunch of it when I see it signed,
which also likely relates to why I'm critical on film portrayals that
often don't capture entire signs.)</li>
<li><em>Ironheart</em> was fun. I like the character and my biggest complaint
was she doesn't get enough moments to bad ass in her own show and it
doesn't seem certain yet she'll get a full movie.</li>
<li><em>Ant-Man and the Wasp: Quantumania</em> gets a lot of discussion in the
section about Kang the Conqueror. In summary it was a big chaotic
nonsense mess, but I found that delightful because all I wanted from
it was to be a big chaotic silly thing. It's Ant-Man.</li>
<li><em>Guardians of the Galaxy Volume 3</em> was a touching swan song to that
version of the Guardians team and to James Gunn leaving Marvel for
good (for now?) to helm their Distinguished Competition. The Guardians
got a good, complete trilogy (especially including the Holiday Special
bridge).</li>
<li><em>Secret Invasion</em> so far has been my only &quot;did not finish&quot; in Marvel
Television. I liked a lot of the reunions in early episodes of SHIELD
and SHIELD-adjacent characters, even knowing the reason most of them
showed up was to get killed off because that is the nature of that
storyline. I also liked that it served no real bearing on
<em>The Marvels</em> despite appearing to do so for homework reasons.</li>
<li><em>The Marvels</em> was fantastic. Almost everything I want in a superhero
team up, but with a team of wilder characters. Seemed to require a
lot of homework so a bunch of people skipped it, as one of the
Marvels first showed up in <em>WandaVision</em> and the other in
<em>Ms. Marvel</em> but everything you need from those appearances is well
summarized in the film, and more reason to watch that stuff <em>after</em>,
if you like both or either character just to get more. Especially
because you are going to fall in love with Ms. Marvel's family and
need a whole TV show with more time with them.</li>
<li><em>Agatha All Along</em> was great. Kathryn Hahn is such a great actress
and gets a lot of fantastic scenery to chew with a fun ensemble
surrounding her to make it wilder.</li>
<li><em>Daredevil: Born Again</em> reminds us of the best parts of the Netflix
Defenders shows, especially Vincent D'Onofrio's Kingpin.</li>
<li><em>Captain America: Brave New World</em> was a fun, silly Captain America
movie and the surprise follow up to the OG MCU Hulk movie we didn't
expect to ever get. It's spoiled in trailers so I don't mind spoiling
it here: come for Harrison Ford getting a chance to play President
again and also Red Hulk, stay for more &quot;Captain America: superhero
buddy cop&quot; fun.</li>
<li><em>Thunderbolts Asterisk Colon Asterisk The New Avengers</em> is a chaotic
movie that asks some deep questions in silly ways about the lines
between inept villain, anti-hero, and
would-be-hero-given-other-circumstances, the nature of superhero
teams both &quot;natural&quot; and &quot;artificial&quot;, and with the ultimate bad guy
being episodes of depression, something you can't just punch. It's
relatable. It's fun. It might remind you, if you are like me, that
neither <em>Black Widow</em> nor <em>Ant-Man and the Wasp</em> (no subtitle, the
second film) are appreciated nearly as much as they should be.</li>
</ol>
<h2>Phase 6 (so far)</h2>
<ol>
<li>I thought <em>Fantastic Four: First Steps</em> delivered exactly the sort of
weirdness I want in a Fantastic Four film. <em>Eternals</em> walked around
the Celestials so that First Steps could run straight to the only
villain 20th Century Fox cared about for the F4 to face but didn't
know how to deliver other than &quot;weird space cloud&quot;.</li>
<li><em>Wonder Man</em> was fantastic. Worth watching. Entirely a standalone,
although it doesn't hurt to have seen <em>Shang Chi</em> or might be reason
to watch <em>Shang Chi</em> after.</li>
<li><em>The Punisher: One Last Kill</em> I liked better than I like a lot of
Punisher stuff (which is mostly that I don't care for it), partly
because it was at turns introspective about how broken the Punisher
is as a character (as in well questioning many of the things I dislike
about the character). Made a particularly weird accidental double
feature with <em>The Bear</em> standalone episode &quot;Gary&quot;. I guess Disney
found a two-for-one sale for Jon Bernthal projects.</li>
</ol>
<p>There's not a lot left in Phase 6 (which seems thin compared to Phases 4
and 5 above): We've got a new Spider-Man film this summer. We've got two
planned Avengers movies on the Holiday Season schedule this year and
next. For Marvel Television there's talk of a third season for
<em>Daredevil: Born Again</em> but no confirmed dates (and just as likely to
slip to Phase 7 timelines?), and a date for <em>VisionQuest</em> which so far
doesn't sound that exciting on paper. That's it on Disney's current
announced schedules for Phase 6.</p>
<h1>What Happens Next for the MCU?</h1>
<p>Disney rearranged a bunch of dates for Phase 7 projects in March which
led to a lot of &quot;Phase 7 movies confirmed&quot; style posts, but really all
anyone can say is &quot;the next two Avengers movies are expected to greatly
shake up the MCU and Disney/Marvel is being tight lipped on what comes
afterward, just talking about dates on projects labeled TBD&quot;.</p>
<p>I've really enjoyed the chaos phases of the MCU. I think they have been
a fun journey and almost every piece has been worth watching on its own.
I don't need the next two Avengers movies to capstone it in a &quot;magic&quot;
way like <em>Avengers: Endgame</em> did. I had enough fun along the way. But
also, maybe I've been lucky in not feeling like I'm watching any of it
for &quot;homework&quot; and perhaps that's also why I haven't really felt
&quot;superhero fatigue&quot;. (I may have also been inoculated against &quot;superhero
fatigue&quot; by the Arrowverse, as it set quite a few bars high and low on
what I am willing to tolerate from my soup operas.) Maybe
<em>Avengers: Doomsday</em> won't be that great, but it doesn't make Phases 4
or 5 worthless, or at least not worth trying to enjoy on their own
without worrying about &quot;how does it all connect?&quot; I'm still very curious
to see what the next few movies do, but I've enjoyed the ride so far
along the way.</p>
]]>
      </content:encoded>
      <category>fanwank</category>
      <pubDate>Sun, 24 May 2026 00:00:00 GMT</pubDate>
    </item>
    <item>
      <title>Gone With The Meme</title>
      <link>https://blog.worldmaker.net/2026/05/21/gone-meme/</link>
      <guid isPermaLink="false">https://blog.worldmaker.net/2026/05/21/gone-meme/</guid>
      <content:encoded>
        <![CDATA[<p><em>Bell Labs, March 1940</em></p>
<p>&quot;Lab Director, would you mind taking a listen to this call we found?&quot;</p>
<p>The earnest appearing tech at my office door seemed shy, but excited.</p>
<p>I didn't prefer to be interrupted in that moment, but it was rare for a
tech to come directly to me in the middle of the afternoon like this for
just a phone call, &quot;What sort of call?&quot;</p>
<p>&quot;Some of the offices forwarded over some sort of strange phone call we
picked up being sent to the weird Area Code 347.&quot;</p>
<p>&quot;What is unusual about that area code?&quot; Sure I work for AT&amp;T, but I'm a
lab director in the research labs, I don't know everything about phone
dialing.</p>
<p>&quot;That area code doesn't exist, it's just not on the map. That's why it
bounced between a few offices before it was sent to us.&quot;</p>
<p>&quot;So it's a garbage call? Noise in the system?&quot; I might as well start
with the obvious questions.</p>
<p>&quot;That's why we'd like your ears on this, Director, before we go chasing
ghosts in the system.&quot;</p>
<p>I shrugged and followed the tech to a workstation where I was handed a
headset and the recording was played for me. An oscilloscope was plugged
in showing the waveforms of the recording as it progressed. It began
with standard dial tones and then proceeded quickly in a mash of what
sounded like line noise but had very visible uniformity on the
oscilloscope. There were some very striking square waves that looked far
too regular to be just line noise. Then it returned to dial tones,
followed by more square waves. If it was garbage, someone had spent a
lot of time crafting it.</p>
<p>Having heard it I took in the name of the workspace and addressed the
tech, &quot;That sure is interesting for garbage, Martin.&quot;</p>
<p>&quot;There's one more twist; there were three more calls after this one to
the same number.&quot;</p>
<p>I sighed, of course there was more to this weird rabbit hole. &quot;What are
your working theories, Martin?&quot;</p>
<p>Martin scratched his chin, &quot;One: Some sort of military encrypted signal
bleeding into the wrong line using a fake area code. Two: Some sort of
buggy IBM mainframe teletype piggy backing on one of our lines. Three:
Some buggy military IBM mainframe combination of the first two. Should
we ask through our defense contracts if this might be one of their
projects? The way it uses our dial tones seems like our proprietary
tech, but doesn't exactly match anything I know of. That would lead me
to suspect Theory One or Three the most, unless IBM is trying to hack
something they shouldn't.&quot;</p>
<p>I grimaced, &quot;Let's try not to get the military involved just yet. Can we
slow this down, get printouts of it, somehow?&quot; I had a technical
background myself in a different specialized mathematics research field
and I was feeling a bit rusty in some of the specifics of the practical
phone systems, but I was certainly intrigued at the wild shapes on that
oscilloscope. Even the sounds, as much as it sounded like line noise had
a regularity to it, a pattern.</p>
<p>&quot;It's too fast.&quot;</p>
<p>&quot;It is too fast?&quot;</p>
<p>Martin looked almost upset, &quot;Faster than any system we have running
today. We could use some mainframe time on this. We need a project code
to charge this to.&quot;</p>
<p>That was the gut punch to the lab's purse strings that I should have
expected, but Martin had done a good job getting me curious enough to
maybe want to spend a bit of money on it, &quot;Well, I guess we're exploring
your second theory. Maybe IBM will tell us it is just another of their
experiments and we won't waste too much more time on it. Or maybe
they'll be just as weirdly intrigued by it to do some leg work on it for
free. I've got some contacts, let me discreetly reach out.&quot;</p>
<p>Back at my desk I dialed my &quot;friend&quot; Bob at IBM. I hated calling Bob for
a number of reasons, including that he was a giant prick, but for as
high up in IBM's red tape he had risen he still had modest engineering
chops and might possibly still chase the possibility of an engineering
puzzle with the eagerness of the nerd he used to be before IBM tried to
remold him into a black suit designed only for budgetary paperwork. He
picked up immediately and like the prick he was started straight into
dominating the conversation, &quot;Hey Maggie. We missed you at the golf
course last week. I hit quite the eagle in the back nine, you should
have seen it. Miss too many more holes and you might fall way behind me.&quot;</p>
<p>I hated playing golf with that crew but keeping a somewhat regular
weekend play schedule with them I felt reminded them that I was worth
any two of them. I tried to steer the conversation back to why I was
calling, &quot;Bob, my lab has picked up an engineering puzzle and we wanted
to make sure it wasn't some sort of IBM skunkwork project accidentally
abusing our lines. Something we think might be coming from one of your
mainframes.&quot;</p>
<p>I filled Bob in on it being from calls to an area code that doesn't
exist, we scheduled a pickup for copies of our tapes of the strange
calls.</p>
<p>Bob showed up to a conference room a couple weeks later with printouts
and a somewhat angry expression, &quot;Maggie why are you wasting our time
investigating some sort of telegraph? This has to be from your side of
the fence with all the dial tones and ASCII.&quot;</p>
<p>&quot;ASCII?&quot; With a brute force like Bob I might as well wait for him to
explain and I glanced at Martin to make sure it was Bob doing the
explaining. It would help Bob blow off some of that steam. He loved
explaining things.</p>
<p>&quot;At IBM we encode text in a well designed encoding we call EBCDIC. You
all at AT&amp;T are the ones that like to insist on the hideous ASCII
because it is backwards compatible with old telegraphs, despite its
obvious deficiencies. You can clearly see some English text in ASCII in
key places of each call.&quot;</p>
<p>Bob pointed to highlighted sections in the ASCII decoded document
including &quot;u up?&quot;, &quot;babe?&quot;, &quot;talk to me&quot;, &quot;plz&quot;.</p>
<p>Bob turned to leave, but then turned back, &quot;The engineers claim they
needed a good break from all the projects for Germany and enjoyed this
test, so I'll invoice you the friends and family rate for the mainframe
time, yeah? Still, though, it's wild, where did AT&amp;T dig up a 25
kilobits per second telegraph?&quot;</p>
<p>Martin almost choked on whatever warm beverage was in his mug, &quot;25
kilobits per second? I knew it was fast, but that's impossibly fast.&quot;</p>
<p>&quot;Roughly,&quot; Bob shrugged. Then Bob's expression changed again to
something like fascinated curiosity, &quot;Wait? This isn't a prank? This
really is some sort of alien telegraph?&quot;</p>
<p>I glanced to Martin and back to Bob, &quot;You really know about as much as
we do. Thanks for the printouts, Bob. We'll let you know if we get more
of these alien telegraph messages.&quot;</p>
<p>Martin returned to my office in a few days with a bunch of analysis and
a few other techs, &quot;We've got a lot of notes and some wild theories,&quot; he
began and I felt like I was all ears.</p>
<p>He starting pointing to repeating patterns near the visible English
words, &quot;A lot of theories keep coming back to these undecodable patterns,
which recur in the plain text sections with such regularity we believe
that this encoding isn't actually ASCII but something derived from it,
using the highest bit in ways we don't expect. We suspect they are still
plain text and not encrypted, though. We don't know what language they
encode, but it seems regular enough to be some form of language. Given
the English words in between and the way the patterns seem to reflect
sentiment, we have suspicions it might be some sort of future
punctuation.&quot;</p>
<p>&quot;Future punctuation? From the future? I think you need to walk me
through these wild theories a bit more,&quot; I had no idea what to think.</p>
<p>&quot;Right, so that was a crazy theory I threw out for a laugh earlier, but
working from there started to unlock some of our other findings,
Director,&quot; Martin looked almost scared to admit the theory had started
from him, &quot;for instance, these other neighboring bytes seem to increment
at the right rate to be timestamps of some sort. We cross-correlated our
phone log data and IBM's notes on these relative timings inside the calls
and they appear to align with an error of only a few wall clock
microseconds.&quot;</p>
<p>&quot;Okay, timestamps, so these are definitely from the future?&quot;</p>
<p>Martin nervously started rambling, as I find so many good techs are easy
to fall into, &quot;They appear to be seconds counted after some sort of
epoch, so we don't know exactly what the epoch is, so we mostly only
know that these events are roughly 54 years after that chosen epoch.
That's too low to be something like the AD epoch, we wouldn't expect an
American telegraph in 54 AD, for instance. If we assume the epoch is
this century and say 1900, telegraphs from 1954 would be wild, but this
also doesn't seem to be tech from only 14 years ahead. We have reason to
suspect the epoch itself may be in our future. So much of the headers
and flow around these text blocks is a control and signalling system
like we would design, but it's message switched. We have enough growing
confidence in the message structure to further affirm that weird future
punctuation is text data and not message metadata. But speaking of
message metadata, that's where we keep finding more weirdness. The
timestamps we found, as mentioned—&quot;</p>
<p>&quot;Right, we've covered the timestamps. They are at least 54 years after
some point we don't know,&quot; I tried not to betray any impatience in my
voice, I just was hoping to short-circuit an accidental loop. &quot;What's
'message switching' and why does its metadata matter?&quot;</p>
<p>Martin took a deep breath, &quot;Message switching is barely a theory right
now: instead of connecting a circuit and allocating all the bandwidth we
need for it up front in theory we could break things like phone calls up
into lots of smaller pieces and float them down large chunks a piece at
a time. A bit like sending lots of little postcards through the mail
instead of a single large book. No one is crazy enough to suggest this
sort of message switching, but if you have roughly 25kbps speed to
communicate with maybe it makes a lot more sense. I can barely imagine
what a full trunk of these sorts of messages would be like. That's a
massive amount of bandwidth, and probably just one trunk among many by
that point! We're lucky that whatever switching system is in charge of
these is only delivering us one specific phone number to phone number
interaction and not a whole regional office worth's. That sort of
bandwidth could have flooded our systems. It is incredible to think
about.&quot;</p>
<p>&quot;So we expect that sort of message switching to be more than 14 years
out?&quot;</p>
<p>&quot;We have no reason to do it now. We don't even have most of the theory
of how it would operate at all yet. The people thinking about that sort
of network design probably aren't even working in this building yet if
this does come out of our labs. Of course, the big smoking gun seems to
be that we found the version number. These seem to be messages from a
Signalling System 7.&quot;</p>
<p>&quot;We're on–?&quot;</p>
<p>&quot;3 mostly, maybe 3.5 if we are feeling generous with ourselves.
Signalling Systems are decades of work both to build and then to roll
out. That's possibly a lot of years. We think that version number is
likely accurate. If we assume the epoch start date is closer to some
future Signalling System release date we're starting to feel like we're
getting glimpses of what feels like the deep future. Right now a
favorite choice is if we built our next Signalling System mid-century,
1950-ish and it was so state of the art we thought it worth numbering
time after that point, and if that's it, are we getting messages from at
least 2004?&quot;</p>
<p>&quot;But in the future they've reverted to some sort of telegraph?&quot; I was
starting to wonder if Bob was maybe right that this was some sort of
prank, but not on him.</p>
<p>&quot;Partly. With weird future punctuation. That gets back to our suspicions
from other parts of the message metadata. We think this can carry audio,
text, video, and other things.&quot; Martin was excited like a puppy, &quot;There
are fewer limits than any system we currently have. 25 kbps is probably
too low for real-time video, but if you send enough messages you could
watch a movie or something. Imagine the possibilities! Imagine the
insane bandwidth needs and how many phone lines they would need for
things like that! No wonder it is trying to message an area code we
don't know about yet.&quot;</p>
<p>The enthusiasm was fascinating and almost infectious, but I was supposed
to be wearing a business-minded hat on this side project, &quot;So we've got
telegraphs from the future? What's the use of this? How much of this
tech can we reverse engineer and start building? How long until we can
send video over phone lines?&quot;</p>
<p>Martin glanced around at his fellow techs who hadn't spoken at all yet,
but apparently now was when they were needed for moral support, &quot;So we
were thinking that we should respond to these telegraphs.&quot;</p>
<p>&quot;You want to try to send telegraphs <em>to</em> the future?&quot; That certainly
wasn't where I was expecting this to go.</p>
<p>&quot;Think of what more we could learn!&quot;</p>
<p>&quot;Wouldn't that be dangerous to the timeline if we learn something out of
order?&quot;</p>
<p>&quot;Weren't you just asking if we could reverse engineer tech out of this?&quot;</p>
<p>I took a few seconds to gather my fury about that response, &quot;Fine, but
do we even have the ability to send messages back?&quot;</p>
<p>Martin indicated the pair that had accompanied him, &quot;We think we can do
it. The dial tones are easy enough and the bootup sequences just after
them seem regular enough we could copy and paste them we think. Or at
least, a single channel of them, we think it's a paired call and
response and we only need to send one channel of the exchange and hope
the other side responds with the same responses. We think we understand
enough of the message format to make a reasonable attempt at faking a
message, and we just have to hope the system on the other end isn't
strict about it. The only real problem is—&quot; Martin stopped and again
looked for some sort of moral support from his peers.</p>
<p>I waited patiently.</p>
<p>Martin finally took a deep breath, &quot;It's still too fast for us. We'd
need some help.&quot;</p>
<p>I sighed, &quot;You want me to waste more money on IBM computing time?&quot;</p>
<p>Martin nodded and I worked on a plan to butter up Bob to help us send an
&quot;alien telegraph&quot; message. I realized it probably meant planning on a
few rounds of golf with that lout and his usual crew.</p>
<p>I was surprised that Bob seemed almost eager to help on this next step,
despite having been angry it wasn't a good, &quot;proper&quot; IBM protocol. I got
the feeling some of his engineers were wasting nearly as much time on
analysis of it as my techs have been. I was less surprised that Bob was
adamant this be a shared effort and the exact message contents must be
work-shopped between all of us. IBM didn't want to just be a dumb
conduit, even after acting like they had nothing to do with this effort.</p>
<p>After more meetings than it should have taken, thanks to some of the
weight of IBM's bureaucracy trying to crush our spirits, the combined
committee settled on trying to send a simple &quot;Who is this?&quot;</p>
<p>All of us were deeply surprised that responses occurred almost
immediately in short succession. IBM was faster this time at getting
printouts back to us, and it was basically the same committee, now with
something of a dedicated conference space in one of my labs. A few of
the IBM engineers had almost taken to working some days entirely from my
lab. (I have few illusions that at least some of it had to do with our
slightly more relaxed dress code. They probably relished any excuse to
dress down.)</p>
<p>Response message 1 was &quot;y shout?&quot; and message 2 was &quot;u srsly gonna new
phone who dis me?&quot; Both had plenty of &quot;future punctuation&quot; following
each. Response message 2 felt like a small win for my techs over the IBM
members of our committee trying to muscle into our turf. It was pleasing
to us to know that whatever this &quot;future telegraph&quot; was they still
called it a phone. We would take the small win there. Response message 1
got the most attention though because it caused some confusion and a lot
of small arguments.</p>
<p>Martin boiled down the working theories on &quot;the shout problem&quot; from
Response message 1 for me a few hours after a debate between him and one
of the IBMers got a bit heated, &quot;Several of them from IBM have a working
theory that we should have included 'alien punctuation' along with our
message and that we should spend a lot more time researching the 'alien
punctuation'. I contend that IBM got sloppy with the teletext
transcription of our message and at the moment they won't admit to any
such thing. It's possible punctuation changed a lot in the future, but
given the overall sloppiness with punctuation in the messages we are
seeing, we don't think a lack of punctuation would appear to be shouting,
we think we have a simpler theory.&quot;</p>
<p>&quot;What's the theory?&quot;</p>
<p>&quot;Most of our teletypes are upper case. Old telegraphs were upper case.
Most of IBM's teletypes are upper case. We think IBM just encoded the
message entirely in upper case.&quot;</p>
<p>&quot;Why would case matter?&quot;</p>
<p>&quot;You've noticed all the messages we've gotten so far are entirely in
lower case, right? Some of us have been debating about that since the
beginning why all the text would be in lowercase, even though we are all
today so used to messages entirely in upper case, and our belief is that
we've come across evidence to fit our theory: if all uppercase is
'shouting', then all lowercase is a, possibly intimate, 'whisper' to
someone 'natively' using text communications. Given the speed of
responses, we think this is something of a text native medium. We just
need to get IBM to admit they encoded nothing but upper case to evidence
our theory. They want to waste more time theorizing about what we call
the 'future punctuation' despite us having no real basis to explore its
patterns, no Rosetta stone of any sort, plus no real belief from us on
the Bell Labs side that a lack of punctuation would mean 'shouting'.
Think about period versus exclamation mark: it's the one that's not the
default that is seen as shouting. That's been somewhat steady in English
text for a long time now. Future changes to punctuation or not.&quot;</p>
<p>It wasn't hard to prod Bob to admit they had indeed teletyped the
message entirely in upper case. I wasn't going to win a war with Bob's
team on entirely ignoring what they called the &quot;alien punctuation&quot;, but
I did get Bob to admit enough defeat that it would be easy next time to
tweak their encoder program to encode only lower case no matter what
teletypewriter they chose to use, so long as we all worked together to
keep trying to find patterns in the &quot;alien punctuation&quot;.</p>
<p>Even with &quot;the shout problem&quot; somewhat resolved amicably, any next
follow up message got swallowed in committee meetings. We agreed that
Response message 1 was probably a rhetorical question given the timing
proximity to Response message 2. No one could agree on an appropriate
response to Response message 2. Some wanted to try to mention that we
were in 1940 and had no idea what area code the other side was trying to
correspond with. Others were worried about the timeline repercussions of
admitting to anything of that sort. Some wanted to just repeat the first
message. Some wanted to ask what year the other person believed it was.
With clear timestamps in the message, even though we didn't know the
epoch date, some of us thought we'd get even more confusion back if we
tried to ask at all about time. Many of the IBMers still thought it
might be aliens so they wanted to ask about distance. They didn't
appreciate my techs pointing out that the number we were sending these
reply messages to was a supposedly ordinary New York City number (and we
felt any questions about distance would probably just get &quot;New York City&quot;
whether or not there were &quot;aliens&quot; at the other end). (We would not want
to confuse our IBM friends by telling them that the NYC return address
was via a trunk line that doesn't exist and our regional office in NYC
still doesn't understand how we are getting dials to nor from it,
despite our pressure to keep digging.)</p>
<p>Our weeks of deliberations were interrupted by Response message 3
arriving. It was simply &quot;c any gud films l8ly?&quot; with no future
punctuation at all. Martin, at my behest, worked very hard not to rub it
into the IBM faces that the &quot;alien punctuation&quot; was neither necessary
nor did a lack of them imply &quot;shouting&quot;.</p>
<p>Thankfully deliberation on a return response to Response message 3 was
quicker and easier than our gridlock in responding to Response message 2.
Terrible grammar aside, &quot;good films&quot; was a pleasing enough conversation
topic. It seemed easy enough to talk about January's big release that
had been dominating our collective watercoolers and so it was decided to
send back &quot;gone with the wind&quot;, emphasizing the lower case text as we'd
all agreed on after &quot;the shout problem&quot;.</p>
<p>Response messages 4 and 5 were also received in short succession.
Response message 5 was a baffling &quot;tbh bae idgaf&quot; that stumped all of us
in the joint committee on alien/future communications. For as baffling
as Response message 5 was, though, Response message 4 also dubbed Binary
Object Alpha, was a huge confusing dump of binary computer noise with a
few strange bits of ASCII embedded that seemed incidental to the overall
binary object.</p>
<p>Where Response message 2 had felt like a small win for us on the &quot;phone
teams&quot;, Binary Object Alpha had lit a strange fire in our IBM
counterparts. Though where we'd not been able to stop talking about
Response message 2, IBM got peculiarly silent and shy any time we tried
to have a frank discussion on Binary Object Alpha, just a weirdly subtle
seeming shift in that they thought they had won some sort of round. It
took a few days for me to corner Bob on a golf course to the get the
&quot;straight talk&quot; out of him on the subject. With a couple of cocktails
and a good few holes below par he was surprisingly forthcoming to me,
&quot;We've been calling Binary Object Alpha the 'future photo'.&quot;</p>
<p>I couldn't help but grin, &quot;So you agree with us now that this is
probably future communications rather than alien communications?&quot;</p>
<p>Bob was surprisingly dark and sober seeming, despite my having loosened
him up a bit, &quot;Yeah, there doesn't seem to be any remaining question on
that on our side.&quot;</p>
<p>I raised my eyebrow at that, because that was quite the statement. I
didn't say anything knowing that Bob was more likely to give me more if
I let him ramble.</p>
<p>Finally he bit into the silence, &quot;It's the wildest thing. More bits of
ASCII, we think we've found a few more timestamps in your weird seconds
past the epoch thing, but other parts of this format feel familiar to us
like they have an unmistakable IBM thumbprint. If AT&amp;T has won some sort
of war that the future of computing looks like 'phones' that message
each other over phone lines like weird telegraphs, we are heartened to
see that IBM still has its fingers on the scales and now ever more
confused how we lost this war. This future photo is a mathematical
marvel. It implies math that IBM hasn't invented yet, but seems likely
to us that it is IBM that will be the company to do so. How do we build
such marvels and still lose in the marketplace of machines? How did we
lose the war? Why are they using telegraph letters and weird timestamps
in this otherwise IBM looking format? Whatever it is, it's not fair, and
management wants us to shutdown future projects with you losers at AT&amp;T
to avoid losing and to try harder to win the future, but so far we've
mostly only agreed to keep a lid on what our R&amp;D employees can tell you
and an agreement to not discuss any technical details of the future
photo beyond that we definitely think it is a photo of some sort.&quot;</p>
<p>I thanked Bob for both the information and the warning that things were
about to get more complicated.</p>
<p>Eventually I circled back around to the work conversation, &quot;So we can't
collaborate on Binary Object Alpha. What about Response message 5? Any
fresh insights on the IBM side? Thoughts on how we proceed? Any
surprises to bring to the next committee meeting?&quot;</p>
<p>Bob hesitated in a manner that I didn't like, &quot;So we may have
inadvertently gotten the military involved.&quot;</p>
<p>&quot;What?&quot;</p>
<p>&quot;An executive may have said the words 'future photo' too loud and to
discourage looking too deeply into Binary Object Alpha may have passed
along the 'encrypted message' challenge of Response message 5.&quot;</p>
<p>&quot;Without other context?&quot;</p>
<p>&quot;Uh, probably.&quot;</p>
<p>&quot;Bob! Get me the name of your military contact you dumped that problem
on.&quot;</p>
<p>I had Martin help lead an effort to draft a clean executive summary of
the messages so far, our findings and speculation. The next &quot;committee
for future communications&quot; was in the office of a Lieutenant Colonel
Collins. Bob and a couple IBMers were there, despite the tension of
being the reason this escalated to a military question and the tension
of whatever was going on with Binary Object Alpha. Collins took his time
reading the executive summary and finished his reading with laughter,
which was not an emotion I expected.</p>
<p>Collins glanced at the assembled rag tag group, &quot;None of you have
clearly spent that much time reading uncensored military communications
such as the military's love of abbreviations including rude ones, I
imagine. You mentioned Gone With The Wind. What's the most memorable
quote from that movie?&quot;</p>
<p>Martin tried his best Clark Gable impression, &quot;Frankly my dear, I don't
give a damn.&quot; That obscenity has been the talk of the town.</p>
<p>Collins pointed to Response message 5, &quot;To be honest, babe, I don't give
a fuck.&quot; Someone gasped at the elevated obscenity. A couple of the
committee groaned at not getting it before when it seemed so obvious
now. &quot;If I had to guess Binary Object Alpha is probably a picture of
Clark Gable, maybe even a movie still from him delivering that line.&quot;
The IBM folks glanced at each other with a weird mix of unpleasant and
pleasant surprise. I supposed they might excited to have a better target
to try to hit in reversing that future math.</p>
<p>It took a few more days of committee debate to find a response that we
all hated the least (&quot;funny&quot;), but just before we could send it we got
Response messages 6 and 7 in short succession &quot;DON'T GHOST ME&quot; and then
&quot;fine, blocked u jerk&quot;. Response message 6 seemed to verify the shouting
theory, but 6 and 7 together seemed to paint a story for which we were
missing pieces. The committee's attempt (a couple more days later) to
send a reply failed with a number disconnected signal for that trunk
line we had been using to connect to the 'future'. Multiple more
attempts were made after that one, in faster succession. I've attached
this narrative summary to the final report of the committee. AT&amp;T's
final hypothesis was that given the speed with which we received many of
these responses from the 'future' it seems to have been a medium for
rapid communications and the many days the committee needed to form
messages to send was a delay that probably violated social norms for
whoever sent us these messages.</p>
<p>We wondered if we'd see more of such messages some day, but still had no
idea how they had arrived in the first place.</p>
]]>
      </content:encoded>
      <category>fiction</category>
      <pubDate>Thu, 21 May 2026 00:00:00 GMT</pubDate>
    </item>
    <item>
      <title>Quick Updates</title>
      <link>https://blog.worldmaker.net/2026/05/09/quick-updates/</link>
      <guid isPermaLink="false">https://blog.worldmaker.net/2026/05/09/quick-updates/</guid>
      <content:encoded>
        <![CDATA[<p>I was skimming old blog articles and realized it
has been a while since I've done small posts. Some
quick updates:</p>
<ul>
<li>I separated from my job in April, so I have my
shingle out and am now looking for new work.
Maybe <a href="https://blog.worldmaker.net/2026/04/29/simple-oxymoron/">given my feelings on recent &quot;AI&quot; trends</a>
I'm looking for a new career.</li>
<li>I procrastinated my job search by spending a
week cleaning my house and then hosting my first
<a href="https://blog.worldmaker.net/tag/thunder/">Thunder</a> party in too long.</li>
<li>I procrastinated my job search by having a lot
of fun building new features for <a href="https://blog.worldmaker.net/2025/04/27/book-club/">my book club's site</a>.
Maybe I'm not entirely done with my career, I
just need someone to trust my creativity. Also,
I had to migrate the site to a different backend.
Tech debt touches everything.</li>
<li>Speaking of tech debt, I procrastinated my job
search yesterday by migrating my entire blog
from Jekyll to <a href="https://lume.land/">Lume</a>. I was tired of Ruby
gem Dependabot warnings that never felt like
they mattered to me, and I was tired of a
preview experience that required a lot of WSL
shenanigans. I've become a big fan of Lume as
a very powerful static site generator. (I'm
doing some pretty complex fun things in that
Book Club site.) It's nice to have good Tags
pages again.</li>
<li><a href="https://portless.sh/">Portless</a> has some Windows quirks but is
handy enough to forgive them.</li>
<li>News of my separation from my job reached me
in the middle of my vacation. It feels weird
that has <a href="https://blog.worldmaker.net/2015/03/03/recent-activity/">happened twice</a> now. I know it is
just business, but it felt deeply personal,
especially given an end date that coincided with
my birthday weekend. I spent the first half of
my vacation having an incredible time at
Disneyland. Disneyland kept asking me questions
about if I have enough creativity in my life.
I kept asking questions why Disney doesn't have
enough creativity in its apps and if I could
find a way to explore that professionally. I
think I was going to be looking for new
challenges this year even if my job hadn't
forced me to consider it the hard way between
an incredible week at Disneyland and a lovely
week on JoCoCruise 2026, both with a lot of
great friends doing awesome things.</li>
</ul>
]]>
      </content:encoded>
      <category>coding</category>
      <category>worldmaker</category>
      <pubDate>Sat, 09 May 2026 00:00:00 GMT</pubDate>
    </item>
    <item>
      <title>The Simple Oxymoron of "AI-Driven Innovation" is Actually Risk Adverse</title>
      <link>https://blog.worldmaker.net/2026/04/29/simple-oxymoron/</link>
      <guid isPermaLink="false">https://blog.worldmaker.net/2026/04/29/simple-oxymoron/</guid>
      <content:encoded>
        <![CDATA[<p>&quot;AI-Driven Innovation&quot; is an oxymoron to excuse over-relying on &quot;legacy
code as a service&quot;. I don't think there is any sort of paradox here in
the way that &quot;AI-Driven Innovation&quot; as a strategy is in consequence risk
adverse and not a strategy for innovation and creativity. I don't see a
need to bury any ledes here and I think this is awful on multiple levels.
I worry it is going to take too many years for companies to realize the
mistakes that they are making right now. It's had me making too many
jokes to hide the depression that I feel that I need to find a new
career altogether.</p>
<p>&quot;AI-Driven Innovation&quot; is an investment in a rental economy of &quot;legacy
code as a service&quot;. Legacy code is always an indicator of some of the
most risk adverse pieces of a company's software portfolio.</p>
<h1>Legacy Code as a Service</h1>
<p>I have spent so much of my career in &quot;the legacy code mines&quot;. It's
always been a skill set I've tried to downplay on my resume. It's always
been a skill set I've kept being asked to return to because I've earned
a lot of experience in it, often the hard way. For decades, I've joked
that my &quot;retirement&quot; plan was to find a cushy high paying, low effort
COBOL job.</p>
<p>But now I don't think those joked about jobs are going to be so easy to
find, not because the LLMs are going to replace all that COBOL code but
that the LLMs are going to help companies build so much more, equivalent
or worse legacy code faster than ever. The LLMs are (unfortunately for
my retirement fancies) commoditizing legacy code. They are making it
plentiful. They are giving tomorrow's legacy code experts more work than
ever before at an unprecedented scale.</p>
<p>I've been accused about being overly unfair to LLMs by calling their
output &quot;legacy code as a service&quot;, but think about it: which companies
offered their truly innovative, proprietary, most modern, most
domain-driven codebases to LLMs for training? LLMs were trained on open
source and a lot of open source is just a funny place to stash &quot;someone
else's legacy code&quot; or at least in the most generous cases &quot;some other
business' complement&quot;.</p>
<p>If AI can't train on the innovative software, how can it help you find
innovation in your own software strategies?</p>
<h1>There is No Creative Ghost in the Machine</h1>
<p>Show me the part of the LLM family of algorithms that is the &quot;creativity&quot;
and I can show you a slot machine and try to teach you about the Gambler's
Fallacy.</p>
<p>People are inherently bad at reasoning about probability-based algorithms
(and probability and statistics math in general) and some of the current
love affair of LLMs seems explicitly to be that people don't understand
the randomness of algorithms deeply associated with probability and are
happy to anthropomorphize it or assign to it a creativity or &quot;surprise&quot;
that is really just random variation on a theme (and eventual regression
to the mean).</p>
<p>You could put a lot of creativity into the input of the system, so called
&quot;prompt engineering&quot;, but what good is laundering that much creativity
through a machine designed to produce the most statistically average
outputs from it?</p>
<p>That of course entirely relies on people seeing &quot;prompt engineering&quot; as
a creative tool for innovation. Most of the companies seeking &quot;AI
Innovation&quot; wrote off software as &quot;overhead&quot; and &quot;cost centers&quot; decades
ago. In a lot of those companies the creativity of the software engineer
is a defect in what should be cogs in a well-oiled machine that takes
business requirements and spits out software. &quot;AI-Driven Innovation&quot;
only continues to further misplace and confuse the sources of creativity
in software development. These companies aren't going to learn anything
from the mistakes they are about to make. (The mistakes they are already
making.)</p>
<h1>There is No Warranty Among Thieves</h1>
<p>When you hire a software engineer, they have an ownership and a
responsibility to the code they write. When working on legacy code, that
person with any sort of ownership is already gone, and that is almost
always an impediment. Some of the key skills on working on a legacy code
codebase are essentially mind-reading, trying to read everything <em>that</em>
person wrote around the time they wrote that software that you can get
some semblance of feeling like you can at least temporarily think like
they used to think in those days on that project.</p>
<p>LLMs are incredible in how much they are a (soylent) sausage factory for
taking all those human pieces (the poetry hidden in the required poetry
forms) and smearing them into a reconstituted meat slurry. The output
code has no ownership and is divorced from any ownership it might have
once had in a gelatinous puree. There's no way to read everything &quot;<em>that</em>
developer&quot; wrote at the time an LLM output what it did. We don't have
access to the full training sets. Even if we did, the major models
themselves fully retrain at a roughly six month cadence and aren't the
same from &quot;version to version&quot;. Sometimes they aren't the same from
month to month due to partial retraining that the model makers don't
feel is worth a version bump. (An LLM is a Ship of Theseus by design.)</p>
<p>Even if you could somehow connect the code that the LLM has produced to
specific open source code via the training set that you don't have access
to, one of the first safety gates in almost every open source license is
a &quot;No Warranty&quot; clause. It's one of the foundations that keeps open
source a viable practice. (Just as software developers generally don't
keep ownership when they are severed from a company is a sort of a
foundational creator of legacy code.) Many open source projects will
offer services similar to a &quot;warranty&quot; or &quot;support contract&quot; for some
material cost (sometimes even just as volunteer effort returned the open
source community). Yet the LLM can't provide even this disclosure, can't
sell you that service, can't tell you what exactly you have &quot;licensed&quot;
or how many &quot;No Warranty&quot; clauses are between you and whoever originally
owned the code it just output. There's not just &quot;no owner&quot; there is a
multi-layer onion of &quot;negative ownership&quot;. Your EULA with the LLM
declares &quot;No Warranty&quot;. Most of the code the LLM trained on declares
&quot;No Warranty&quot;. Who really owns anything? It's all just a rental to some
landlord, as a <em>service</em>.</p>
<p>Then there is the added awful layer in that onion that much of the code
the LLM trained on was also effectively stolen, according to the licenses
and agreements in place around that code. A lot of the open source world
is built on &quot;copyleft&quot; licenses designed to legally encourage &quot;what you
take from the open source community you volunteer to bring back and
continue to enrich the community with your own contributions&quot;. LLMs
today are ignoring these legal requirements. LLMs are not attempting to
enrich the community with their sausage factories, they are trying to
claim that they don't need to because the sausage is so different from
the animals it was processed out of. In the process of reconstituting
that meat slurry they claim that they've liberated it from such concerns
as &quot;this is an illegal slaughterhouse&quot; and &quot;this is a health code
violation&quot; and &quot;this is just generally a violation of common sense human
decency&quot;.</p>
<p>Is that a great foundation for &quot;AI-Driven Innovation&quot;? Does a
nasty-smelling onion of &quot;no one owns this code&quot;, &quot;no one wants to know
what the real ingredients in this code are&quot;, and &quot;if they did know all
the ingredients in this it would be illegal&quot; sound like &quot;innovation&quot;?
There was an era in American economics when companies innovated on how
much diseased rat meat they could get away with in their sausage. I
can't be alone in thinking this will all end poorly. I'm also sure I'm
not alone in thinking this won't end poorly fast enough to save us from
so many of the worst mistakes.</p>
<h1>Garbage In, Garbage Out</h1>
<p>&quot;AI-Driven Innovation&quot; is a garbage plan of creating garbage inputs to
software that produce nothing but statistically probable legacy code, as
a service. It's a simple oxymoron. There's no real &quot;innovation&quot; to be
found in the output. It's going to keep checking boxes in PowerPoint
slides to an investor class that has bought into &quot;AI-Driven Innovation&quot;
as a shared delusion of their (inherent disconnect from) reality rather
than accepting it to be a sad improbable dream that makes no sense when
easily questioned. In the meantime a lot of software is going to regress
to some ugly averages and real software innovation will take a huge hit.</p>
<p>In the mean time, the truly innovative, experienced software engineers
are encouraged to accept layoffs if they can't &quot;align&quot; on &quot;AI-Driven
Innovation&quot;. (Leaving plenty of now immediately legacy code behind in so
doing.) In the mean time, tomorrow's truly innovative software engineers
are told to avoid growing real experience and instead feed LLMs to
generate so much mediocrity, if they have jobs at all. In the mean time,
&quot;AI-Driven Innovation&quot; is crafting a mesmerizing illusion that software
as a labor practice is over and companies don't really need software
developers at all.</p>
<p>Garbage in, garbage out, in a feedback loop threatening to spiral to
destroy real innovation in software development.</p>
]]>
      </content:encoded>
      <category>coding</category>
      <pubDate>Wed, 29 Apr 2026 00:00:00 GMT</pubDate>
    </item>
    <item>
      <title>A Pattern for Redux with Epics in C# Dependency Injection</title>
      <link>https://blog.worldmaker.net/2026/04/07/redux-net/</link>
      <guid isPermaLink="false">https://blog.worldmaker.net/2026/04/07/redux-net/</guid>
      <content:encoded>
        <![CDATA[<p>This is a day job project that for a bit I thought might spin out into
an open source library but the funny thing about it is that it has been
more of a chisel the statue out of the marble type of project. It
started from a complicated place and the further we've pushed it towards
Redux best practices the more it disappears into &quot;just&quot; being primarily
a pattern of C#'s out-of-the-box dependency injection.</p>
<p>(For some small context, the original design that this design migrated
away from was far more inspired as something of a telephone game to
Angular's libraries such as NgRx, which themselves were a telephone
game away from Redux.)</p>
<p>It still seems worth publicly documenting for the next team looking for
this pattern. I wish there was a library I could point to to install
this pattern into your project, hopefully this documentation will
suffice as a good starting point. To some extent, the lack of a need for
an external library is a small triumph and a proof that this can be a
good way to build your C# &quot;view state&quot; model, for projects that want a
ReactiveX-&quot;native&quot; alternative to state management and/or event bus
patterns such as MVC, MVVM, Mediatr.</p>
<h1><code>RootAction</code></h1>
<p>The core type for this Redux design is <code>RootAction</code>. This is not a
particularly great name, but it does avoid conflict with
<code>System.Action&lt;T&gt;</code> (the <code>void</code>-relative of <code>System.Func&lt;T, U&gt;</code>). In
proper Redux fashion, this is a very simple type with only a required
field name <code>Type</code>.</p>
<pre class="shiki laserwave" style="background-color:#27212e;color:#ffffff" tabindex="0"><code class="language-cs"><span class="line"><span style="color:#FFFFFF">public </span><span style="color:#A96BC0">record</span><span style="color:#A96BC0"> RootAction</span></span>
<span class="line"><span style="color:#7B6995">{</span></span>
<span class="line"><span style="color:#FFFFFF">    public virtual </span><span style="color:#FFFFFF">string</span><span style="color:#FFFFFF"> Type </span><span style="color:#74DFC4">=></span><span style="color:#7B6995"> $"</span><span style="color:#7B6995">{</span><span style="color:#EB64B9">GetType</span><span style="color:#7B6995">()</span><span style="color:#74DFC4">.</span><span style="color:#FFF">Name</span><span style="color:#7B6995">}</span><span style="color:#7B6995">"</span><span style="color:#7B6995">;</span></span>
<span class="line"><span style="color:#7B6995">}</span></span></code></pre>
<p>There are a bunch of strong notes to be made here, some of which remained
debates for some time after moving to this pattern. The NgRx family never
fully understood the importance of Actions as a serializable type, but
it is a useful core principle to many Redux pattern benefits. Serializable
types can be output to logs for replay, for instance.</p>
<p>Related to replay, that is something the day job project never got around
to implementing. At one point Source Generators were explored to replace
the reflection-based <code>Type</code> seen here with something more useful to
<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>
<p>The advantage to this form of base <code>Type</code> implementation was that a tree
of types was relatively easy to build. Example:</p>
<pre class="shiki laserwave" style="background-color:#27212e;color:#ffffff" tabindex="0"><code class="language-cs"><span class="line"><span style="color:#FFFFFF">public </span><span style="color:#A96BC0">record</span><span style="color:#A96BC0"> CoolFeatureAction</span><span style="color:#7B6995"> :</span><span style="color:#A96BC0"> RootAction</span></span>
<span class="line"><span style="color:#7B6995">{</span></span>
<span class="line"><span style="color:#FFFFFF">    public override </span><span style="color:#FFFFFF">string</span><span style="color:#FFFFFF"> Type </span><span style="color:#74DFC4">=></span><span style="color:#7B6995"> $"</span><span style="color:#B4DCE7">CoolFeature/</span><span style="color:#7B6995">{</span><span style="color:#EB64B9">GetType</span><span style="color:#7B6995">()</span><span style="color:#74DFC4">.</span><span style="color:#FFF">Name</span><span style="color:#7B6995">}</span><span style="color:#7B6995">"</span></span>
<span class="line"><span style="color:#FFFFFF">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#FFFFFF">public sealed </span><span style="color:#A96BC0">record</span><span style="color:#A96BC0"> CoolFeatureInitialized</span><span style="color:#7B6995">(</span><span style="color:#FFFFFF">bool IsCool</span><span style="color:#7B6995">)</span><span style="color:#7B6995"> :</span><span style="color:#A96BC0"> CoolFeatureAction</span><span style="color:#7B6995">;</span></span></code></pre>
<p>One of the things that this also highlights is that the Redux world
best practice of writing &quot;action creators&quot; is handled automatically by
C# record syntax. The primary record constructor is already laid out as
a Redux-like &quot;action creator&quot;, and provides similar refactoring benefits.</p>
<p>A few of the other tips about Actions from the Redux pattern best
practices that may be useful to keep in mind at this point: Actions are
generally named like Events, they are named as the &quot;user interaction that
just happened&quot; in the past tense. Actions should generally not be &quot;mere
setters&quot; (<code>FieldASet</code>, <code>FieldBSet</code>) but rather State <em>transactions</em> as
a high-level event (<code>EntireFeatureInitialized</code> with everything needed
to update <code>FieldA</code> and <code>FieldB</code> all at once).</p>
<h2>Option: Popping the Middle Layer of Actions</h2>
<p>Another brief digression from JSON polymorphic debates was if the
<code>RootAction</code>'s <code>Type</code> property implementation should have been, assuming
Reflection was still fine:</p>
<pre class="shiki laserwave" style="background-color:#27212e;color:#ffffff" tabindex="0"><code class="language-cs"><span class="line"><span style="color:#FFFFFF">public </span><span style="color:#A96BC0">record</span><span style="color:#A96BC0"> RootAction</span></span>
<span class="line"><span style="color:#7B6995">{</span></span>
<span class="line"><span style="color:#FFFFFF">    public </span><span style="color:#FFFFFF">string</span><span style="color:#FFFFFF"> Type </span><span style="color:#74DFC4">=></span><span style="color:#7B6995"> $"</span><span style="color:#7B6995">{</span><span style="color:#EB64B9">GetType</span><span style="color:#7B6995">()</span><span style="color:#74DFC4">.</span><span style="color:#FFF">FullName</span><span style="color:#74DFC4">.</span><span style="color:#EB64B9">Replace</span><span style="color:#7B6995">(</span><span style="color:#7B6995">'</span><span style="color:#B4DCE7">.</span><span style="color:#7B6995">'</span><span style="color:#7B6995">,</span><span style="color:#7B6995"> '</span><span style="color:#B4DCE7">/</span><span style="color:#7B6995">'</span><span style="color:#7B6995">)}</span><span style="color:#7B6995">"</span><span style="color:#7B6995">;</span></span>
<span class="line"><span style="color:#7B6995">}</span></span></code></pre>
<p>In which case the inheritance tree might have been a little flatter in
exchange for using the namespace hierarchy automatically. (With the idea
of the <code>Replace</code> being to still prefer the Redux dev tools suggested <code>/</code>
namespace separator instead of .NET's <code>.</code>.)</p>
<h2>Performance Digression on Reference Types</h2>
<p>In part from references libraries that heavily relied on .NET's value
types (typically, but not just, <code>readonly record struct</code>) to deeply
avoid nulls (at the risk of sometimes equally confusing &quot;zeroed&quot;
<code>default(T)</code> from structs) the application had picked up the premature
optimization of using value types for a lot of internal state data.</p>
<p>This was a performance problem that we caught early in the Redux
pattern refactor. It is easy to forget that value types are generally
passed by value which means <em>by copy</em> in many cases. Especially in a Redux
design you often can't afford all the memory of every State and every
Action to be copied for every Reducer (and Epic and view component, etc).</p>
<p>Don't forget that <code>public record</code> is shorter than
<code>public readonly record struct</code> for a good reason and is a good default.
Reference types aren't an enemy to performance in .NET, they are just as
often the solution. Don't forget to apply performance profiling tools
for real data from your applications and not just default to value types
because they sometimes help performance or null safety.</p>
<h1>Reducers</h1>
<p>The heart of the Redux pattern is Reducers to apply Actions to State. In
C# we used a very simple <code>IReducer</code> definition from which a number of
things inherited, include a <code>Reducer&lt;Action, State&gt;</code> base type for single
Action reducers.</p>
<pre class="shiki laserwave" style="background-color:#27212e;color:#ffffff" tabindex="0"><code class="language-cs"><span class="line"><span style="color:#FFFFFF">public </span><span style="color:#A96BC0">interface</span><span style="color:#A96BC0"> IReducer</span><span style="color:#7B6995">&#x3C;</span><span style="color:#A96BC0">State</span><span style="color:#7B6995">></span></span>
<span class="line"><span style="color:#7B6995">{</span></span>
<span class="line"><span style="color:#FFFFFF">    public </span><span style="color:#A96BC0">State</span><span style="color:#EB64B9"> Handle</span><span style="color:#7B6995">(</span><span style="color:#A96BC0">RootAction</span><span style="color:#FFFFFF"> action</span><span style="color:#7B6995">,</span><span style="color:#A96BC0"> State</span><span style="color:#FFFFFF"> state</span><span style="color:#7B6995">);</span></span>
<span class="line"><span style="color:#7B6995">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#FFFFFF">public abstract </span><span style="color:#A96BC0">class</span><span style="color:#A96BC0"> Reducer</span><span style="color:#7B6995">&#x3C;</span><span style="color:#A96BC0">Action</span><span style="color:#7B6995">,</span><span style="color:#A96BC0"> State</span><span style="color:#7B6995">></span></span>
<span class="line"><span style="color:#7B6995">    :</span><span style="color:#A96BC0"> IReducer</span><span style="color:#7B6995">&#x3C;</span><span style="color:#A96BC0">State</span><span style="color:#7B6995">></span><span style="color:#FFFFFF"> where </span><span style="color:#A96BC0">Action</span><span style="color:#7B6995"> :</span><span style="color:#A96BC0"> RootAction</span></span>
<span class="line"><span style="color:#7B6995">{</span></span>
<span class="line"><span style="color:#FFFFFF">    public abstract </span><span style="color:#A96BC0">State</span><span style="color:#EB64B9"> Reduce</span><span style="color:#7B6995">(</span><span style="color:#A96BC0">Action</span><span style="color:#FFFFFF"> action</span><span style="color:#7B6995">,</span><span style="color:#A96BC0"> State</span><span style="color:#FFFFFF"> state</span><span style="color:#7B6995">);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#FFFFFF">    public </span><span style="color:#A96BC0">State</span><span style="color:#EB64B9"> Handle</span><span style="color:#7B6995">(</span><span style="color:#A96BC0">RootAction</span><span style="color:#FFFFFF"> rootAction</span><span style="color:#7B6995">,</span><span style="color:#A96BC0"> State</span><span style="color:#FFFFFF"> state</span><span style="color:#7B6995">)</span><span style="color:#74DFC4"> =></span><span style="color:#FFF"> rootAction</span><span style="color:#40B4C4"> switch</span></span>
<span class="line"><span style="color:#7B6995">    {</span></span>
<span class="line"><span style="color:#A96BC0">        Action</span><span style="color:#FFFFFF"> action </span><span style="color:#74DFC4">=></span><span style="color:#EB64B9"> Reduce</span><span style="color:#7B6995">(</span><span style="color:#FFF">action</span><span style="color:#7B6995">,</span><span style="color:#FFF"> state</span><span style="color:#7B6995">),</span></span>
<span class="line"><span style="color:#FFFFFF">        _ </span><span style="color:#74DFC4">=></span><span style="color:#FFF"> state</span><span style="color:#7B6995">,</span></span>
<span class="line"><span style="color:#7B6995">    };</span></span>
<span class="line"><span style="color:#7B6995">}</span></span></code></pre>
<p>The base class is a pattern in C# you also see in things like Authorization
Policy Handlers, a type-safe &quot;trampoline&quot; from the generic <code>RootAction</code>
to a specific <code>Action</code> (we had less of a problem with a type parameter
sometimes shadowing <code>System.Action&lt;T&gt;</code> than a top-level type doing so).
(This is also Typescript influence here of dropping the <code>T</code>-prefix
semi-Hungarian notation for type parameters; though as with most C#
naming schemes the <code>I</code>-prefix for interfaces is maybe eternal at this
point.)</p>
<p>Because the trampoline is often the shortest way to write many Reducers,
the <code>IReducer&lt;State&gt;</code> method borrows the name <code>Handle</code> from places like
Policy Handlers so that <code>Reducer&lt;Action, State&gt;</code> can use the more domain
friendly <code>Reduce</code> as its more specific abstract function.</p>
<p>It's also important to note that <code>IReducer&lt;State&gt;</code> is intentionally and
<em>only</em> synchronous. That's an ancient Redux best practice. That will also
be something we return to as we get to Epics.</p>
<p>To round out our <code>CoolFeature</code> example we might have an example Reducer
like so:</p>
<pre class="shiki laserwave" style="background-color:#27212e;color:#ffffff" tabindex="0"><code class="language-cs"><span class="line"><span style="color:#FFFFFF">public </span><span style="color:#A96BC0">record</span><span style="color:#A96BC0"> CoolThing</span><span style="color:#7B6995">(</span><span style="color:#FFFFFF">string</span><span style="color:#FFFFFF"> Name</span><span style="color:#7B6995">,</span><span style="color:#91889B"> /* … */</span><span style="color:#A96BC0"> DateTimeOffset</span><span style="color:#FFFFFF"> Created</span><span style="color:#7B6995">);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#FFFFFF">public </span><span style="color:#A96BC0">record</span><span style="color:#A96BC0"> CoolFeatureState</span><span style="color:#7B6995">(</span></span>
<span class="line"><span style="color:#FFFFFF">    bool IsCool</span><span style="color:#7B6995">,</span></span>
<span class="line"><span style="color:#A96BC0">    DateTimeOffset</span><span style="color:#FFFFFF"> CoolStarted</span><span style="color:#7B6995">,</span></span>
<span class="line"><span style="color:#A96BC0">    IEnumerable</span><span style="color:#7B6995">&#x3C;</span><span style="color:#A96BC0">CoolThing</span><span style="color:#7B6995">></span><span style="color:#FFFFFF"> CoolThings</span><span style="color:#7B6995">)</span></span>
<span class="line"><span style="color:#7B6995">{</span></span>
<span class="line"><span style="color:#FFFFFF">    public static readonly </span><span style="color:#A96BC0">CoolFeatureState</span><span style="color:#FFFFFF"> Disabled </span><span style="color:#74DFC4">=</span><span style="color:#74DFC4"> new</span><span style="color:#7B6995">(</span><span style="color:#FFE261">false</span><span style="color:#7B6995">,</span><span style="color:#FFF"> DateTimeOffset</span><span style="color:#74DFC4">.</span><span style="color:#FFF">MinValue</span><span style="color:#7B6995">,</span><span style="color:#7B6995"> []);</span></span>
<span class="line"><span style="color:#7B6995">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#FFFFFF">public </span><span style="color:#A96BC0">class</span><span style="color:#A96BC0"> CoolFeatureInitializedReducer</span><span style="color:#7B6995">(</span><span style="color:#A96BC0">TimeProvider</span><span style="color:#FFFFFF"> timeProvider</span><span style="color:#7B6995">)</span></span>
<span class="line"><span style="color:#7B6995">    :</span><span style="color:#A96BC0"> Reducer</span><span style="color:#7B6995">&#x3C;</span><span style="color:#A96BC0">CoolFeatureInitialized</span><span style="color:#7B6995">,</span><span style="color:#A96BC0"> CoolFeatureState</span><span style="color:#7B6995">></span></span>
<span class="line"><span style="color:#7B6995">{</span></span>
<span class="line"><span style="color:#FFFFFF">    public override </span><span style="color:#A96BC0">CoolFeatureState</span><span style="color:#EB64B9"> Reduce</span><span style="color:#7B6995">(</span></span>
<span class="line"><span style="color:#A96BC0">        CoolFeatureInitialized</span><span style="color:#FFFFFF"> action</span><span style="color:#7B6995">,</span></span>
<span class="line"><span style="color:#A96BC0">        CoolFeatureState</span><span style="color:#FFFFFF"> state</span><span style="color:#7B6995">)</span><span style="color:#74DFC4"> =></span><span style="color:#FFF"> state</span><span style="color:#74DFC4"> with</span></span>
<span class="line"><span style="color:#7B6995">    {</span></span>
<span class="line"><span style="color:#FFF">        IsCool</span><span style="color:#74DFC4"> =</span><span style="color:#FFF"> action</span><span style="color:#74DFC4">.</span><span style="color:#FFF">IsCool</span><span style="color:#7B6995">,</span></span>
<span class="line"><span style="color:#FFF">        CoolStarted</span><span style="color:#74DFC4"> =</span><span style="color:#FFF"> timeProvider</span><span style="color:#74DFC4">.</span><span style="color:#EB64B9">GetLocalNow</span><span style="color:#7B6995">(),</span></span>
<span class="line"><span style="color:#91889B">        // ASIDE: Injecting a TimeProvider is a great way to increase</span></span>
<span class="line"><span style="color:#91889B">        // unit testing powers of this reducer because you can test in</span></span>
<span class="line"><span style="color:#91889B">        // fake time with a FakeTimeProvider.</span></span>
<span class="line"><span style="color:#7B6995">    };</span></span>
<span class="line"><span style="color:#7B6995">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#FFFFFF">public static </span><span style="color:#A96BC0">class</span><span style="color:#A96BC0"> CoolFeatureExtensions</span></span>
<span class="line"><span style="color:#7B6995">{</span></span>
<span class="line"><span style="color:#FFFFFF">    public static </span><span style="color:#A96BC0">IServiceCollection</span><span style="color:#EB64B9"> AddCoolFeature</span><span style="color:#7B6995">(</span><span style="color:#FFFFFF">this </span><span style="color:#A96BC0">IServiceCollection</span><span style="color:#FFFFFF"> services</span><span style="color:#7B6995">)</span><span style="color:#74DFC4"> =></span><span style="color:#FFF"> services</span></span>
<span class="line"><span style="color:#74DFC4">        .</span><span style="color:#EB64B9">AddTransient</span><span style="color:#7B6995">&#x3C;</span><span style="color:#A96BC0">IReducer</span><span style="color:#7B6995">&#x3C;</span><span style="color:#A96BC0">CoolFeatureState</span><span style="color:#7B6995">>,</span><span style="color:#A96BC0"> CoolFeatureInitializedReducer</span><span style="color:#7B6995">>();</span></span>
<span class="line"><span style="color:#7B6995">}</span></span></code></pre>
<p>Note that while Reducers are required to be synchronous, they are still
free to inject and use other dependencies (so long as they are also
synchronous). Similarly, reducers may be state machines that use complex
calculations based on the current state to produce the next state. The
big restriction is <em>synchronous</em>.</p>
<h1>Important Digression into <code>Lens&lt;A, B&gt;</code></h1>
<p>Before we can talk about the Store and Slices, we need one more tool in
our toolbox.</p>
<p>In Typescript, ala the original Redux pattern, we can easily &quot;slice&quot; a
State via a string key of that state. Typescript is also friendly in that
this can be relatively type safe thanks to meta-typing tools like
<code>T[keyof T]</code>.</p>
<p>Doing that type-safely in C# is a bit more work. One of the tools we can
use to do it that is useful as a generic type for immutable data structures
can be borrowed from functional programming and is called a Lens. A Lens
is really just a type-safe <em>getter</em> from one item to another (given an
<code>A</code>, I want a <code>B</code>), and a type-safe <em>setter</em> in the other direction
(given a <code>B</code>, I would like an updated <code>A</code>).</p>
<p>There are several functional programming libraries that provide a
<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>
really is just as simple as described, so we can also just implement our
own quite simply:</p>
<pre class="shiki laserwave" style="background-color:#27212e;color:#ffffff" tabindex="0"><code class="language-cs"><span class="line"><span style="color:#FFFFFF">public </span><span style="color:#A96BC0">record</span><span style="color:#A96BC0"> Lens</span><span style="color:#7B6995">&#x3C;</span><span style="color:#A96BC0">A</span><span style="color:#7B6995">,</span><span style="color:#A96BC0"> B</span><span style="color:#7B6995">>(</span></span>
<span class="line"><span style="color:#A96BC0">    Func</span><span style="color:#7B6995">&#x3C;</span><span style="color:#A96BC0">A</span><span style="color:#7B6995">,</span><span style="color:#A96BC0"> B</span><span style="color:#7B6995">></span><span style="color:#FFFFFF"> Get</span><span style="color:#7B6995">,</span></span>
<span class="line"><span style="color:#A96BC0">    Func</span><span style="color:#7B6995">&#x3C;</span><span style="color:#A96BC0">B</span><span style="color:#7B6995">,</span><span style="color:#A96BC0"> Func</span><span style="color:#7B6995">&#x3C;</span><span style="color:#A96BC0">A</span><span style="color:#7B6995">,</span><span style="color:#A96BC0"> A</span><span style="color:#7B6995">>></span><span style="color:#FFFFFF"> SetF</span></span>
<span class="line"><span style="color:#7B6995">)</span></span>
<span class="line"><span style="color:#7B6995">{</span></span>
<span class="line"><span style="color:#FFFFFF">    public </span><span style="color:#A96BC0">A</span><span style="color:#EB64B9"> Set</span><span style="color:#7B6995">(</span><span style="color:#A96BC0">B</span><span style="color:#FFFFFF"> value</span><span style="color:#7B6995">,</span><span style="color:#A96BC0"> A</span><span style="color:#FFFFFF"> container</span><span style="color:#7B6995">)</span><span style="color:#74DFC4"> =></span><span style="color:#EB64B9"> SetF</span><span style="color:#7B6995">(</span><span style="color:#FFF">value</span><span style="color:#7B6995">)(</span><span style="color:#FFF">container</span><span style="color:#7B6995">);</span></span>
<span class="line"><span style="color:#7B6995">}</span></span></code></pre>
<p>There's a few other convenience functions you could implement (often
named <code>Update</code>), but that's the important heart of <code>Lens&lt;A, B&gt;</code>.</p>
<p>The importance of <code>SetF</code> in this form as <code>Func&lt;B, Func&lt;A, A&gt;&gt;</code> as the
underlying <em>setter</em> is maybe not obvious if you haven't spent enough time
in languages that support currying such as F# and Haskell, but is one of
the useful properties for making <code>Lens&lt;A, B&gt;</code> generically useful to
several of our needs.</p>
<p>One useful way to see it from Redux points of view is that
<code>SetF(someFixedValue)</code> creates just about the most simple type of Reducer
possible (given a State, yield the next State). It's a handy abstraction.</p>
<h1>Handy Digression into <code>ShareReplay</code></h1>
<p>We are taking a ReactiveX native approach to our Store that follows to
encourage consistent use of Epics (more on them later). I bounce enough
between RxJS and classic C# ReactiveX that I find these utility extension
methods handy to keep around to save my poor memory some small bit of
trouble:</p>
<pre class="shiki laserwave" style="background-color:#27212e;color:#ffffff" tabindex="0"><code class="language-cs"><span class="line"><span style="color:#FFFFFF">public static </span><span style="color:#A96BC0">class</span><span style="color:#A96BC0"> ObservableExtensions</span></span>
<span class="line"><span style="color:#7B6995">{</span></span>
<span class="line"><span style="color:#FFFFFF">    public static </span><span style="color:#A96BC0">IObservable</span><span style="color:#7B6995">&#x3C;</span><span style="color:#A96BC0">T</span><span style="color:#7B6995">></span><span style="color:#EB64B9"> Share</span><span style="color:#7B6995">&#x3C;</span><span style="color:#A96BC0">T</span><span style="color:#7B6995">>(</span><span style="color:#FFFFFF">this </span><span style="color:#A96BC0">IObservable</span><span style="color:#7B6995">&#x3C;</span><span style="color:#A96BC0">T</span><span style="color:#7B6995">></span><span style="color:#FFFFFF"> source</span><span style="color:#7B6995">)</span><span style="color:#74DFC4"> =></span></span>
<span class="line"><span style="color:#FFF">        source</span><span style="color:#74DFC4">.</span><span style="color:#EB64B9">Publish</span><span style="color:#7B6995">()</span><span style="color:#74DFC4">.</span><span style="color:#EB64B9">RefCount</span><span style="color:#7B6995">();</span></span>
<span class="line"></span>
<span class="line"><span style="color:#FFFFFF">    public static </span><span style="color:#A96BC0">IObservable</span><span style="color:#7B6995">&#x3C;</span><span style="color:#A96BC0">T</span><span style="color:#7B6995">></span><span style="color:#EB64B9"> ShareReplay</span><span style="color:#7B6995">&#x3C;</span><span style="color:#A96BC0">T</span><span style="color:#7B6995">>(</span></span>
<span class="line"><span style="color:#FFFFFF">        this </span><span style="color:#A96BC0">IObservable</span><span style="color:#7B6995">&#x3C;</span><span style="color:#A96BC0">T</span><span style="color:#7B6995">></span><span style="color:#FFFFFF"> source</span><span style="color:#7B6995">,</span></span>
<span class="line"><span style="color:#FFFFFF">        int bufferCount</span><span style="color:#7B6995">)</span><span style="color:#74DFC4"> =></span></span>
<span class="line"><span style="color:#FFF">        source</span><span style="color:#74DFC4">.</span><span style="color:#EB64B9">Replay</span><span style="color:#7B6995">(</span><span style="color:#FFF">bufferCount</span><span style="color:#7B6995">)</span><span style="color:#74DFC4">.</span><span style="color:#EB64B9">RefCount</span><span style="color:#7B6995">();</span></span>
<span class="line"><span style="color:#7B6995">}</span></span></code></pre>
<p>The RxJS names are little more evocative to me than remembering when to
use <code>RefCount()</code>.</p>
<h1><code>ISlice&lt;State&gt;</code></h1>
<p>Redux best practices are to use a single Store to collect your entire
application state. This allows for easier debugging serialization and
centralization of some forms of developer tooling.</p>
<p>But you often don't want to reason with an entire application state all
at once. You are often working on a specific vertical feature and want
to write your Reducers and Epics (more on those later) from the
perspective of only a <em>slice</em> of your Store.</p>
<p>We start with the general interface design:</p>
<pre class="shiki laserwave" style="background-color:#27212e;color:#ffffff" tabindex="0"><code class="language-cs"><span class="line"><span style="color:#FFFFFF">public </span><span style="color:#A96BC0">interface</span><span style="color:#A96BC0"> ISlice</span><span style="color:#7B6995">&#x3C;</span><span style="color:#A96BC0">State</span><span style="color:#7B6995">></span></span>
<span class="line"><span style="color:#7B6995">{</span></span>
<span class="line"><span style="color:#FFFFFF">    public </span><span style="color:#A96BC0">IObservable</span><span style="color:#7B6995">&#x3C;</span><span style="color:#A96BC0">State</span><span style="color:#7B6995">></span><span style="color:#FFFFFF"> States </span><span style="color:#7B6995">{</span><span style="color:#A96BC0"> get</span><span style="color:#7B6995">;</span><span style="color:#7B6995"> }</span></span>
<span class="line"><span style="color:#7B6995">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#FFFFFF">public </span><span style="color:#A96BC0">interface</span><span style="color:#A96BC0"> ISubSlice</span><span style="color:#7B6995">&#x3C;</span><span style="color:#A96BC0">ParentState</span><span style="color:#7B6995">></span><span style="color:#7B6995"> :</span><span style="color:#A96BC0"> IReducer</span><span style="color:#7B6995">&#x3C;</span><span style="color:#A96BC0">ParentState</span><span style="color:#7B6995">></span></span>
<span class="line"><span style="color:#7B6995">{</span></span>
<span class="line"><span style="color:#FFFFFF">    public void </span><span style="color:#EB64B9">RegisterParentSlice</span><span style="color:#7B6995">(</span><span style="color:#A96BC0">ISlice</span><span style="color:#7B6995">&#x3C;</span><span style="color:#A96BC0">ParentState</span><span style="color:#7B6995">></span><span style="color:#FFFFFF"> parent</span><span style="color:#7B6995">);</span></span>
<span class="line"><span style="color:#7B6995">}</span></span></code></pre>
<p><code>ISubSlice&lt;ParentState&gt;</code> is the stranger of the two interfaces in this
design because it is designed to facilitate the dependency injection of
circular references. Each Slice can be seen as a large reducer in its
own parent. Each Slice also needs a reference to its parent to be able
to do that slicing work of outputting the subset of states relevant to
the slice.</p>
<p>The implementation of Slice follows from this dependency injection trick
plus one other simple one: the .NET DI container like many DI containers
supports injecting <code>IEnumerable&lt;T&gt;</code> to get all implementations of an
interface that have been injected.</p>
<pre class="shiki laserwave" style="background-color:#27212e;color:#ffffff" tabindex="0"><code class="language-cs"><span class="line"><span style="color:#FFFFFF">public </span><span style="color:#A96BC0">class</span><span style="color:#A96BC0"> Slice</span><span style="color:#7B6995">&#x3C;</span><span style="color:#A96BC0">ParentState</span><span style="color:#7B6995">,</span><span style="color:#A96BC0"> State</span><span style="color:#7B6995">></span></span>
<span class="line"><span style="color:#7B6995">    :</span><span style="color:#A96BC0"> ISlice</span><span style="color:#7B6995">&#x3C;</span><span style="color:#A96BC0">State</span><span style="color:#7B6995">>,</span><span style="color:#A96BC0"> ISubSlice</span><span style="color:#7B6995">&#x3C;</span><span style="color:#A96BC0">ParentState</span><span style="color:#7B6995">>,</span><span style="color:#A96BC0"> IDisposable</span></span>
<span class="line"><span style="color:#7B6995">{</span></span>
<span class="line"><span style="color:#FFFFFF">    private readonly </span><span style="color:#A96BC0">IEnumerable</span><span style="color:#7B6995">&#x3C;</span><span style="color:#A96BC0">IReducer</span><span style="color:#7B6995">&#x3C;</span><span style="color:#A96BC0">State</span><span style="color:#7B6995">>></span><span style="color:#FFFFFF"> reducers</span><span style="color:#7B6995">;</span></span>
<span class="line"><span style="color:#FFFFFF">    private readonly </span><span style="color:#A96BC0">Lens</span><span style="color:#7B6995">&#x3C;</span><span style="color:#A96BC0">ParentState</span><span style="color:#7B6995">,</span><span style="color:#A96BC0"> State</span><span style="color:#7B6995">></span><span style="color:#FFFFFF"> lens</span><span style="color:#7B6995">;</span></span>
<span class="line"><span style="color:#FFFFFF">    private readonly </span><span style="color:#A96BC0">BehaviorSubject</span><span style="color:#7B6995">&#x3C;</span><span style="color:#A96BC0">ISlice</span><span style="color:#7B6995">&#x3C;</span><span style="color:#A96BC0">ParentState</span><span style="color:#7B6995">>?></span><span style="color:#FFFFFF"> parentSlice </span><span style="color:#74DFC4">=</span><span style="color:#74DFC4"> new</span><span style="color:#7B6995">(</span><span style="color:#FFE261">null</span><span style="color:#7B6995">);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#FFFFFF">    public </span><span style="color:#A96BC0">IObservable</span><span style="color:#7B6995">&#x3C;</span><span style="color:#A96BC0">State</span><span style="color:#7B6995">></span><span style="color:#FFFFFF"> States </span><span style="color:#74DFC4">=></span><span style="color:#FFF"> parentSlice</span></span>
<span class="line"><span style="color:#74DFC4">        .</span><span style="color:#EB64B9">Select</span><span style="color:#7B6995">(</span><span style="color:#FFFFFF">parent </span><span style="color:#74DFC4">=></span><span style="color:#FFF"> parent</span><span style="color:#74DFC4"> is</span><span style="color:#FFE261"> null</span></span>
<span class="line"><span style="color:#74DFC4">            ?</span><span style="color:#FFF"> Observable</span><span style="color:#74DFC4">.</span><span style="color:#EB64B9">Empty</span><span style="color:#7B6995">&#x3C;</span><span style="color:#A96BC0">State</span><span style="color:#7B6995">>()</span></span>
<span class="line"><span style="color:#74DFC4">            :</span><span style="color:#FFF"> parent</span><span style="color:#74DFC4">.</span><span style="color:#FFF">States</span></span>
<span class="line"><span style="color:#74DFC4">                .</span><span style="color:#EB64B9">Select</span><span style="color:#7B6995">(</span><span style="color:#FFF">lens</span><span style="color:#74DFC4">.</span><span style="color:#FFF">Get</span><span style="color:#7B6995">)</span></span>
<span class="line"><span style="color:#74DFC4">                .</span><span style="color:#EB64B9">DistinctUntilChanged</span><span style="color:#7B6995">())</span></span>
<span class="line"><span style="color:#74DFC4">        .</span><span style="color:#EB64B9">Switch</span><span style="color:#7B6995">()</span></span>
<span class="line"><span style="color:#74DFC4">        .</span><span style="color:#EB64B9">ShareReplay</span><span style="color:#7B6995">(</span><span style="color:#B381C5">1</span><span style="color:#7B6995">);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#FFFFFF">    public void </span><span style="color:#EB64B9">RegisterParentSlice</span><span style="color:#7B6995">(</span><span style="color:#A96BC0">ISlice</span><span style="color:#7B6995">&#x3C;</span><span style="color:#A96BC0">ParentState</span><span style="color:#7B6995">></span><span style="color:#FFFFFF"> parent</span><span style="color:#7B6995">)</span><span style="color:#74DFC4"> =></span></span>
<span class="line"><span style="color:#FFF">        parentSlice</span><span style="color:#74DFC4">.</span><span style="color:#EB64B9">Next</span><span style="color:#7B6995">(</span><span style="color:#FFF">parent</span><span style="color:#7B6995">);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#FFFFFF">    public </span><span style="color:#EB64B9">Slice</span><span style="color:#7B6995">(</span></span>
<span class="line"><span style="color:#A96BC0">        IEnumerable</span><span style="color:#7B6995">&#x3C;</span><span style="color:#A96BC0">IReducer</span><span style="color:#7B6995">&#x3C;</span><span style="color:#A96BC0">State</span><span style="color:#7B6995">>></span><span style="color:#FFFFFF"> reducers</span><span style="color:#7B6995">,</span></span>
<span class="line"><span style="color:#A96BC0">        Lens</span><span style="color:#7B6995">&#x3C;</span><span style="color:#A96BC0">ParentState</span><span style="color:#7B6995">,</span><span style="color:#A96BC0"> State</span><span style="color:#7B6995">></span><span style="color:#FFFFFF"> lens</span></span>
<span class="line"><span style="color:#7B6995">    )</span></span>
<span class="line"><span style="color:#7B6995">    {</span></span>
<span class="line"><span style="color:#FFE261">        this</span><span style="color:#74DFC4">.</span><span style="color:#FFF">reducers</span><span style="color:#74DFC4"> =</span><span style="color:#FFF"> reducers</span><span style="color:#7B6995">;</span></span>
<span class="line"><span style="color:#FFE261">        this</span><span style="color:#74DFC4">.</span><span style="color:#FFF">lens</span><span style="color:#74DFC4"> =</span><span style="color:#FFF"> lens</span><span style="color:#7B6995">;</span></span>
<span class="line"><span style="color:#40B4C4">        foreach</span><span style="color:#7B6995"> (</span><span style="color:#A96BC0">var</span><span style="color:#FFFFFF"> reducer </span><span style="color:#40B4C4">in</span><span style="color:#FFE261"> this</span><span style="color:#74DFC4">.</span><span style="color:#FFF">reducers</span><span style="color:#7B6995">)</span></span>
<span class="line"><span style="color:#7B6995">        {</span></span>
<span class="line"><span style="color:#40B4C4">            if</span><span style="color:#7B6995"> (</span><span style="color:#FFF">reducer</span><span style="color:#74DFC4"> is</span><span style="color:#A96BC0"> ISubSlice</span><span style="color:#7B6995">&#x3C;</span><span style="color:#A96BC0">State</span><span style="color:#7B6995">></span><span style="color:#FFFFFF"> subslice</span><span style="color:#7B6995">)</span></span>
<span class="line"><span style="color:#7B6995">            {</span></span>
<span class="line"><span style="color:#FFF">                subslice</span><span style="color:#74DFC4">.</span><span style="color:#EB64B9">RegisterParentSlice</span><span style="color:#7B6995">(</span><span style="color:#FFE261">this</span><span style="color:#7B6995">);</span></span>
<span class="line"><span style="color:#7B6995">            }</span></span>
<span class="line"><span style="color:#7B6995">        }</span></span>
<span class="line"><span style="color:#7B6995">    }</span></span>
<span class="line"></span>
<span class="line"><span style="color:#FFFFFF">    public </span><span style="color:#A96BC0">ParentState</span><span style="color:#EB64B9"> Handle</span><span style="color:#7B6995">(</span><span style="color:#A96BC0">RootAction</span><span style="color:#FFFFFF"> action</span><span style="color:#7B6995">,</span><span style="color:#A96BC0"> ParentState</span><span style="color:#FFFFFF"> parent</span><span style="color:#7B6995">)</span><span style="color:#74DFC4"> =></span></span>
<span class="line"><span style="color:#FFF">        lens</span><span style="color:#74DFC4">.</span><span style="color:#EB64B9">Set</span><span style="color:#7B6995">(</span></span>
<span class="line"><span style="color:#FFF">            reducers</span><span style="color:#74DFC4">.</span><span style="color:#EB64B9">Aggregate</span><span style="color:#7B6995">(</span><span style="color:#FFF">lens</span><span style="color:#74DFC4">.</span><span style="color:#EB64B9">Get</span><span style="color:#7B6995">(</span><span style="color:#FFF">parent</span><span style="color:#7B6995">),</span><span style="color:#7B6995"> (</span><span style="color:#FFFFFF">acc</span><span style="color:#7B6995">,</span><span style="color:#FFFFFF"> reducer</span><span style="color:#7B6995">)</span><span style="color:#74DFC4"> =></span></span>
<span class="line"><span style="color:#FFF">                reducer</span><span style="color:#74DFC4">.</span><span style="color:#EB64B9">Handle</span><span style="color:#7B6995">(</span><span style="color:#FFF">action</span><span style="color:#7B6995">,</span><span style="color:#FFF"> acc</span><span style="color:#7B6995">)),</span></span>
<span class="line"><span style="color:#FFF">            parent</span><span style="color:#7B6995">);</span></span>
<span class="line"><span style="color:#91889B">        // or with an Update helper implemented:</span></span>
<span class="line"><span style="color:#91889B">        // lens.Update(parent, parentState =></span></span>
<span class="line"><span style="color:#91889B">        //   reducers.Aggregate(parentState, (acc, reducer) =></span></span>
<span class="line"><span style="color:#91889B">        //     reducer.Handle(action, acc)))</span></span>
<span class="line"></span>
<span class="line"><span style="color:#FFFFFF">    public void </span><span style="color:#EB64B9">Dispose</span><span style="color:#7B6995">()</span></span>
<span class="line"><span style="color:#7B6995">    {</span></span>
<span class="line"><span style="color:#FFF">        GC</span><span style="color:#74DFC4">.</span><span style="color:#EB64B9">SuppressFinalize</span><span style="color:#7B6995">(</span><span style="color:#FFE261">this</span><span style="color:#7B6995">);</span></span>
<span class="line"><span style="color:#FFF">        parentSlice</span><span style="color:#74DFC4">.</span><span style="color:#EB64B9">Dispose</span><span style="color:#7B6995">();</span></span>
<span class="line"><span style="color:#7B6995">    }</span></span>
<span class="line"><span style="color:#7B6995">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#FFFFFF">public static </span><span style="color:#A96BC0">class</span><span style="color:#A96BC0"> SliceExtensions</span></span>
<span class="line"><span style="color:#7B6995">{</span></span>
<span class="line"><span style="color:#FFFFFF">    public static </span><span style="color:#A96BC0">IServiceCollection</span><span style="color:#EB64B9"> AddSlice</span><span style="color:#7B6995">&#x3C;</span><span style="color:#A96BC0">ParentState</span><span style="color:#7B6995">,</span><span style="color:#A96BC0"> State</span><span style="color:#7B6995">>(</span></span>
<span class="line"><span style="color:#A96BC0">        Lens</span><span style="color:#7B6995">&#x3C;</span><span style="color:#A96BC0">ParentState</span><span style="color:#7B6995">,</span><span style="color:#A96BC0"> State</span><span style="color:#7B6995">></span><span style="color:#FFFFFF"> lens</span><span style="color:#7B6995">)</span><span style="color:#74DFC4"> =></span><span style="color:#FFF"> services</span></span>
<span class="line"><span style="color:#74DFC4">            .</span><span style="color:#EB64B9">AddSingleton</span><span style="color:#7B6995">(</span><span style="color:#FFFFFF">provider </span><span style="color:#74DFC4">=></span><span style="color:#74DFC4"> new</span><span style="color:#A96BC0"> Slice</span><span style="color:#7B6995">(</span></span>
<span class="line"><span style="color:#FFF">                provider</span><span style="color:#74DFC4">.</span><span style="color:#EB64B9">GetServices</span><span style="color:#7B6995">&#x3C;</span><span style="color:#A96BC0">IReducer</span><span style="color:#7B6995">&#x3C;</span><span style="color:#A96BC0">State</span><span style="color:#7B6995">>>(),</span></span>
<span class="line"><span style="color:#FFF">                lens</span><span style="color:#7B6995">))</span></span>
<span class="line"><span style="color:#91889B">            // allow direct injection IObservable&#x3C;State></span></span>
<span class="line"><span style="color:#74DFC4">            .</span><span style="color:#EB64B9">AddSingleton</span><span style="color:#7B6995">(</span><span style="color:#FFFFFF">provider </span><span style="color:#74DFC4">=></span><span style="color:#FFF"> provider</span></span>
<span class="line"><span style="color:#74DFC4">                .</span><span style="color:#EB64B9">GetRequiredService</span><span style="color:#7B6995">&#x3C;</span><span style="color:#A96BC0">Slice</span><span style="color:#7B6995">&#x3C;</span><span style="color:#A96BC0">ParentState</span><span style="color:#7B6995">,</span><span style="color:#A96BC0"> State</span><span style="color:#7B6995">>>()</span></span>
<span class="line"><span style="color:#74DFC4">                .</span><span style="color:#FFF">States</span><span style="color:#7B6995">)</span></span>
<span class="line"><span style="color:#74DFC4">            .</span><span style="color:#EB64B9">AddSingleton</span><span style="color:#7B6995">&#x3C;</span><span style="color:#A96BC0">ISlice</span><span style="color:#7B6995">&#x3C;</span><span style="color:#A96BC0">State</span><span style="color:#7B6995">>>(</span><span style="color:#FFFFFF">provider </span><span style="color:#74DFC4">=></span><span style="color:#FFF"> provider</span></span>
<span class="line"><span style="color:#74DFC4">                .</span><span style="color:#EB64B9">GetRequiredService</span><span style="color:#7B6995">&#x3C;</span><span style="color:#A96BC0">Slice</span><span style="color:#7B6995">&#x3C;</span><span style="color:#A96BC0">ParentState</span><span style="color:#7B6995">,</span><span style="color:#A96BC0"> State</span><span style="color:#7B6995">>>())</span></span>
<span class="line"><span style="color:#91889B">            // register for pickup by its parent</span></span>
<span class="line"><span style="color:#74DFC4">            .</span><span style="color:#EB64B9">AddSingleton</span><span style="color:#7B6995">&#x3C;</span><span style="color:#A96BC0">IReducer</span><span style="color:#7B6995">&#x3C;</span><span style="color:#A96BC0">ParentState</span><span style="color:#7B6995">>>(</span><span style="color:#FFFFFF">provider </span><span style="color:#74DFC4">=></span><span style="color:#FFF"> provider</span></span>
<span class="line"><span style="color:#74DFC4">                .</span><span style="color:#EB64B9">GetRequiredService</span><span style="color:#7B6995">&#x3C;</span><span style="color:#A96BC0">Slice</span><span style="color:#7B6995">&#x3C;</span><span style="color:#A96BC0">ParentState</span><span style="color:#7B6995">,</span><span style="color:#A96BC0"> State</span><span style="color:#7B6995">>>());</span></span>
<span class="line"><span style="color:#7B6995">}</span></span></code></pre>
<p>Our <code>CoolFeatureState</code> might be part of a larger <code>RootState</code>:</p>
<pre class="shiki laserwave" style="background-color:#27212e;color:#ffffff" tabindex="0"><code class="language-cs"><span class="line"><span style="color:#FFFFFF">public </span><span style="color:#A96BC0">record</span><span style="color:#A96BC0"> RootState</span><span style="color:#7B6995">(</span><span style="color:#A96BC0">CoolFeatureState</span><span style="color:#FFFFFF"> CoolFeature</span><span style="color:#7B6995">)</span></span>
<span class="line"><span style="color:#7B6995">{</span></span>
<span class="line"><span style="color:#FFFFFF">    public static readonly </span><span style="color:#A96BC0">RootState</span><span style="color:#FFFFFF"> Initial </span><span style="color:#74DFC4">=</span><span style="color:#74DFC4"> new</span><span style="color:#7B6995">(</span><span style="color:#FFF">CoolFeatureState</span><span style="color:#74DFC4">.</span><span style="color:#FFF">Disabled</span><span style="color:#7B6995">);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#FFFFFF">    public static readonly </span><span style="color:#A96BC0">Lens</span><span style="color:#7B6995">&#x3C;</span><span style="color:#A96BC0">RootState</span><span style="color:#7B6995">,</span><span style="color:#A96BC0"> CoolFeatureState</span><span style="color:#7B6995">></span><span style="color:#FFFFFF"> CoolFeatureLens </span><span style="color:#74DFC4">=></span></span>
<span class="line"><span style="color:#74DFC4">        new</span><span style="color:#7B6995">(</span><span style="color:#FFFFFF">state </span><span style="color:#74DFC4">=></span><span style="color:#FFF"> state</span><span style="color:#74DFC4">.</span><span style="color:#FFF">CoolFeature</span><span style="color:#7B6995">,</span><span style="color:#FFFFFF"> state </span><span style="color:#74DFC4">=></span><span style="color:#FFFFFF"> coolFeature </span><span style="color:#74DFC4">=></span><span style="color:#FFF"> state</span><span style="color:#74DFC4"> with</span></span>
<span class="line"><span style="color:#7B6995">        {</span></span>
<span class="line"><span style="color:#FFF">            CoolFeature</span><span style="color:#74DFC4"> =</span><span style="color:#FFF"> coolFeature</span><span style="color:#7B6995">,</span></span>
<span class="line"><span style="color:#7B6995">        });</span></span>
<span class="line"><span style="color:#7B6995">}</span></span></code></pre>
<p>With Slice implemented we can fill out our example <code>CoolFeatureExtensions</code>
with the next registration piece:</p>
<pre class="shiki laserwave" style="background-color:#27212e;color:#ffffff" tabindex="0"><code class="language-cs"><span class="line"><span style="color:#FFFFFF">public static </span><span style="color:#A96BC0">class</span><span style="color:#A96BC0"> CoolFeatureExtensions</span></span>
<span class="line"><span style="color:#7B6995">{</span></span>
<span class="line"><span style="color:#FFFFFF">    public static </span><span style="color:#A96BC0">IServiceCollection</span><span style="color:#EB64B9"> AddCoolFeature</span><span style="color:#7B6995">(</span><span style="color:#FFFFFF">this </span><span style="color:#A96BC0">IServiceCollection</span><span style="color:#FFFFFF"> services</span><span style="color:#7B6995">)</span><span style="color:#74DFC4"> =></span><span style="color:#FFF"> services</span></span>
<span class="line"><span style="color:#74DFC4">        .</span><span style="color:#EB64B9">AddTransient</span><span style="color:#7B6995">&#x3C;</span><span style="color:#A96BC0">IReducer</span><span style="color:#7B6995">&#x3C;</span><span style="color:#A96BC0">CoolFeatureState</span><span style="color:#7B6995">>,</span><span style="color:#A96BC0"> CoolFeatureInitializedReducer</span><span style="color:#7B6995">>()</span></span>
<span class="line"><span style="color:#74DFC4">        .</span><span style="color:#EB64B9">AddSlice</span><span style="color:#7B6995">(</span><span style="color:#FFF">RootState</span><span style="color:#74DFC4">.</span><span style="color:#FFF">CoolFeatureLens</span><span style="color:#7B6995">);</span></span>
<span class="line"><span style="color:#7B6995">}</span></span></code></pre>
<p>Note that both generic parameters to <code>AddSlice</code> are cleanly picked up from
<code>Lens&lt;A, B&gt;</code> matching those parameters.</p>
<h1><code>Store</code></h1>
<p>I think the implementation of our central Redux Store follows pretty
directly from our Slice implementation above:</p>
<pre class="shiki laserwave" style="background-color:#27212e;color:#ffffff" tabindex="0"><code class="language-cs"><span class="line"><span style="color:#FFFFFF">public </span><span style="color:#FFF">IActions</span></span>
<span class="line"><span style="color:#7B6995">{</span></span>
<span class="line"><span style="color:#FFF">    public</span><span style="color:#A96BC0"> IObservable</span><span style="color:#7B6995">&#x3C;</span><span style="color:#A96BC0">RootAction</span><span style="color:#7B6995">></span><span style="color:#FFFFFF"> Actions</span><span style="color:#7B6995">;</span></span>
<span class="line"><span style="color:#7B6995">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#FFFFFF">public </span><span style="color:#FFF">IDispatch</span></span>
<span class="line"><span style="color:#7B6995">{</span></span>
<span class="line"><span style="color:#FFF">    public</span><span style="color:#FFFFFF"> void </span><span style="color:#EB64B9">Dispatch</span><span style="color:#7B6995">(</span><span style="color:#A96BC0">RootAction</span><span style="color:#FFFFFF"> action</span><span style="color:#7B6995">);</span></span>
<span class="line"><span style="color:#7B6995">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#FFFFFF">public sealed </span><span style="color:#A96BC0">class</span><span style="color:#A96BC0"> Store</span></span>
<span class="line"><span style="color:#7B6995">    :</span><span style="color:#A96BC0"> ISlice</span><span style="color:#7B6995">&#x3C;</span><span style="color:#A96BC0">RootState</span><span style="color:#7B6995">>,</span><span style="color:#A96BC0"> IActions</span><span style="color:#7B6995">,</span><span style="color:#A96BC0"> IDispatch</span><span style="color:#7B6995">,</span><span style="color:#A96BC0"> IDisposable</span></span>
<span class="line"><span style="color:#7B6995">{</span></span>
<span class="line"><span style="color:#FFFFFF">    private readonly </span><span style="color:#A96BC0">IEnumerable</span><span style="color:#7B6995">&#x3C;</span><span style="color:#A96BC0">IReducer</span><span style="color:#7B6995">&#x3C;</span><span style="color:#A96BC0">RootState</span><span style="color:#7B6995">>></span><span style="color:#FFFFFF"> reducers</span><span style="color:#7B6995">;</span></span>
<span class="line"><span style="color:#FFFFFF">    private readonly </span><span style="color:#A96BC0">Subject</span><span style="color:#7B6995">&#x3C;</span><span style="color:#A96BC0">RootAction</span><span style="color:#7B6995">></span><span style="color:#FFFFFF"> actions </span><span style="color:#74DFC4">=</span><span style="color:#74DFC4"> new</span><span style="color:#7B6995">();</span></span>
<span class="line"><span style="color:#FFFFFF">    private readonly </span><span style="color:#A96BC0">BehaviorSubject</span><span style="color:#7B6995">&#x3C;</span><span style="color:#A96BC0">RootState</span><span style="color:#7B6995">></span><span style="color:#FFFFFF"> states </span><span style="color:#74DFC4">=</span><span style="color:#74DFC4"> new</span><span style="color:#7B6995">(</span><span style="color:#FFF">RootState</span><span style="color:#74DFC4">.</span><span style="color:#FFF">Initial</span><span style="color:#7B6995">);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#FFFFFF">    public </span><span style="color:#A96BC0">IObservable</span><span style="color:#7B6995">&#x3C;</span><span style="color:#A96BC0">RootState</span><span style="color:#7B6995">></span><span style="color:#FFFFFF"> States </span><span style="color:#74DFC4">=></span><span style="color:#FFF"> states</span><span style="color:#7B6995">;</span></span>
<span class="line"><span style="color:#FFFFFF">    public </span><span style="color:#A96BC0">IObservable</span><span style="color:#7B6995">&#x3C;</span><span style="color:#A96BC0">RootAction</span><span style="color:#7B6995">></span><span style="color:#FFFFFF"> Actions </span><span style="color:#74DFC4">=></span><span style="color:#FFF"> actions</span><span style="color:#7B6995">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#FFFFFF">    public </span><span style="color:#EB64B9">Store</span><span style="color:#7B6995">(</span><span style="color:#A96BC0">IEnumerable</span><span style="color:#7B6995">&#x3C;</span><span style="color:#A96BC0">IReducer</span><span style="color:#7B6995">&#x3C;</span><span style="color:#A96BC0">RootState</span><span style="color:#7B6995">>></span><span style="color:#FFFFFF"> reducers</span><span style="color:#7B6995">)</span></span>
<span class="line"><span style="color:#7B6995">    {</span></span>
<span class="line"><span style="color:#FFE261">        this</span><span style="color:#74DFC4">.</span><span style="color:#FFF">reducers</span><span style="color:#74DFC4"> =</span><span style="color:#FFF"> reducers</span><span style="color:#7B6995">;</span></span>
<span class="line"><span style="color:#40B4C4">        foreach</span><span style="color:#7B6995"> (</span><span style="color:#A96BC0">var</span><span style="color:#FFFFFF"> reducer </span><span style="color:#40B4C4">in</span><span style="color:#FFF"> reducers</span><span style="color:#7B6995">)</span></span>
<span class="line"><span style="color:#7B6995">        {</span></span>
<span class="line"><span style="color:#40B4C4">            if</span><span style="color:#7B6995"> (</span><span style="color:#FFF">reducer</span><span style="color:#74DFC4"> is</span><span style="color:#A96BC0"> ISubSlice</span><span style="color:#7B6995">&#x3C;</span><span style="color:#A96BC0">RootState</span><span style="color:#7B6995">></span><span style="color:#FFFFFF"> subslice</span><span style="color:#7B6995">)</span></span>
<span class="line"><span style="color:#7B6995">            {</span></span>
<span class="line"><span style="color:#FFF">                subslice</span><span style="color:#74DFC4">.</span><span style="color:#EB64B9">RegisterParentSlice</span><span style="color:#7B6995">(</span><span style="color:#FFE261">this</span><span style="color:#7B6995">);</span></span>
<span class="line"><span style="color:#7B6995">            }</span></span>
<span class="line"><span style="color:#7B6995">        }</span></span>
<span class="line"><span style="color:#7B6995">    }</span></span>
<span class="line"></span>
<span class="line"><span style="color:#FFFFFF">    public void </span><span style="color:#EB64B9">Dispatch</span><span style="color:#7B6995">(</span><span style="color:#A96BC0">RootAction</span><span style="color:#FFFFFF"> action</span><span style="color:#7B6995">)</span></span>
<span class="line"><span style="color:#7B6995">    {</span></span>
<span class="line"><span style="color:#A96BC0">        var</span><span style="color:#FFFFFF"> nextState </span><span style="color:#74DFC4">=</span><span style="color:#FFF"> reducers</span><span style="color:#74DFC4">.</span><span style="color:#EB64B9">Aggregate</span><span style="color:#7B6995">(</span><span style="color:#FFF">states</span><span style="color:#74DFC4">.</span><span style="color:#FFF">Value</span><span style="color:#7B6995">,</span><span style="color:#7B6995"> (</span><span style="color:#FFFFFF">acc</span><span style="color:#7B6995">,</span><span style="color:#FFFFFF"> reducer</span><span style="color:#7B6995">)</span><span style="color:#74DFC4"> =></span></span>
<span class="line"><span style="color:#FFF">            reducer</span><span style="color:#74DFC4">.</span><span style="color:#EB64B9">Handle</span><span style="color:#7B6995">(</span><span style="color:#FFF">action</span><span style="color:#7B6995">,</span><span style="color:#FFF"> acc</span><span style="color:#7B6995">))</span></span>
<span class="line"><span style="color:#EB64B9">        if</span><span style="color:#7B6995"> (</span><span style="color:#FFF">nextState</span><span style="color:#74DFC4"> !=</span><span style="color:#FFF"> states</span><span style="color:#74DFC4">.</span><span style="color:#FFF">Value</span><span style="color:#7B6995">)</span></span>
<span class="line"><span style="color:#7B6995">        {</span></span>
<span class="line"><span style="color:#FFF">            states</span><span style="color:#74DFC4">.</span><span style="color:#EB64B9">OnNext</span><span style="color:#7B6995">(</span><span style="color:#FFF">nextState</span><span style="color:#7B6995">)</span><span style="color:#FFFFFF">;</span></span>
<span class="line"><span style="color:#7B6995">        }</span></span>
<span class="line"><span style="color:#FFF">        actions</span><span style="color:#74DFC4">.</span><span style="color:#EB64B9">OnNext</span><span style="color:#7B6995">(</span><span style="color:#FFF">action</span><span style="color:#7B6995">);</span></span>
<span class="line"><span style="color:#7B6995">    }</span></span>
<span class="line"></span>
<span class="line"><span style="color:#FFFFFF">    public void </span><span style="color:#EB64B9">IDispose</span><span style="color:#7B6995">()</span></span>
<span class="line"><span style="color:#7B6995">    {</span></span>
<span class="line"><span style="color:#FFF">        GC</span><span style="color:#74DFC4">.</span><span style="color:#EB64B9">SuppressFinalize</span><span style="color:#7B6995">(</span><span style="color:#FFE261">this</span><span style="color:#7B6995">);</span></span>
<span class="line"><span style="color:#FFF">        states</span><span style="color:#74DFC4">.</span><span style="color:#EB64B9">Dispose</span><span style="color:#7B6995">();</span></span>
<span class="line"><span style="color:#FFF">        actions</span><span style="color:#74DFC4">.</span><span style="color:#EB64B9">Dispose</span><span style="color:#7B6995">();</span></span>
<span class="line"><span style="color:#7B6995">    }</span></span>
<span class="line"><span style="color:#7B6995">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#FFFFFF">public static </span><span style="color:#A96BC0">class</span><span style="color:#A96BC0"> StoreExtensions</span></span>
<span class="line"><span style="color:#7B6995">{</span></span>
<span class="line"><span style="color:#FFFFFF">    public static </span><span style="color:#A96BC0">IServiceCollection</span><span style="color:#EB64B9"> AddStore</span><span style="color:#7B6995">(</span><span style="color:#FFFFFF">this </span><span style="color:#A96BC0">IServiceCollection</span><span style="color:#FFFFFF"> services</span><span style="color:#7B6995">)</span><span style="color:#74DFC4"> =></span><span style="color:#FFF"> services</span></span>
<span class="line"><span style="color:#74DFC4">        .</span><span style="color:#EB64B9">AddCoolFeature</span><span style="color:#7B6995">()</span></span>
<span class="line"><span style="color:#74DFC4">        .</span><span style="color:#EB64B9">AddSingleton</span><span style="color:#7B6995">&#x3C;</span><span style="color:#A96BC0">Store</span><span style="color:#7B6995">>()</span></span>
<span class="line"><span style="color:#74DFC4">        .</span><span style="color:#EB64B9">AddSingleton</span><span style="color:#7B6995">(</span><span style="color:#FFFFFF">provider </span><span style="color:#74DFC4">=></span><span style="color:#FFF"> provider</span><span style="color:#74DFC4">.</span><span style="color:#EB64B9">GetRequiredService</span><span style="color:#7B6995">&#x3C;</span><span style="color:#A96BC0">Store</span><span style="color:#7B6995">>()</span><span style="color:#74DFC4">.</span><span style="color:#FFF">States</span><span style="color:#7B6995">)</span></span>
<span class="line"><span style="color:#74DFC4">        .</span><span style="color:#EB64B9">AddSingleton</span><span style="color:#7B6995">&#x3C;</span><span style="color:#A96BC0">ISlice</span><span style="color:#7B6995">&#x3C;</span><span style="color:#A96BC0">RootState</span><span style="color:#7B6995">>>(</span><span style="color:#FFFFFF">provider </span><span style="color:#74DFC4">=></span><span style="color:#FFF"> provider</span><span style="color:#74DFC4">.</span><span style="color:#EB64B9">GetRequiredService</span><span style="color:#7B6995">&#x3C;</span><span style="color:#A96BC0">Store</span><span style="color:#7B6995">>())</span></span>
<span class="line"><span style="color:#74DFC4">        .</span><span style="color:#EB64B9">AddSingleton</span><span style="color:#7B6995">(</span><span style="color:#FFFFFF">provider </span><span style="color:#74DFC4">=></span><span style="color:#FFF"> provider</span><span style="color:#74DFC4">.</span><span style="color:#EB64B9">GetRequiredService</span><span style="color:#7B6995">&#x3C;</span><span style="color:#A96BC0">Store</span><span style="color:#7B6995">>()</span><span style="color:#74DFC4">.</span><span style="color:#FFF">Actions</span><span style="color:#7B6995">)</span></span>
<span class="line"><span style="color:#74DFC4">        .</span><span style="color:#EB64B9">AddSingleton</span><span style="color:#7B6995">&#x3C;</span><span style="color:#A96BC0">IActions</span><span style="color:#7B6995">>(</span><span style="color:#FFFFFF">provider </span><span style="color:#74DFC4">=></span><span style="color:#FFF"> provider</span><span style="color:#74DFC4">.</span><span style="color:#EB64B9">GetRequiredService</span><span style="color:#7B6995">&#x3C;</span><span style="color:#A96BC0">Store</span><span style="color:#7B6995">>())</span></span>
<span class="line"><span style="color:#74DFC4">        .</span><span style="color:#EB64B9">AddSingleton</span><span style="color:#7B6995">&#x3C;</span><span style="color:#A96BC0">IDispatch</span><span style="color:#7B6995">>(</span><span style="color:#FFFFFF">provider </span><span style="color:#74DFC4">=></span><span style="color:#FFF"> provider</span><span style="color:#74DFC4">.</span><span style="color:#EB64B9">GetRequiredService</span><span style="color:#7B6995">&#x3C;</span><span style="color:#A96BC0">Store</span><span style="color:#7B6995">>());</span></span>
<span class="line"><span style="color:#7B6995">}</span></span></code></pre>
<h1><code>IEpic&lt;State&gt;</code></h1>
<p>The final part to this pattern that does a surprising amount of heavy
lifting is the concept of an Epic. Named after &quot;lengthy story&quot;, an Epic
is the asynchronous workflow relative of the Reducer. Where Reducers
must be synchronous, the Epic can do anything Observables can do,
including calling <code>async/await</code> methods and running all sorts of side
effects.</p>
<p>Generally Epics observe Actions, the current state after an Action, and
return Actions.</p>
<pre class="shiki laserwave" style="background-color:#27212e;color:#ffffff" tabindex="0"><code class="language-cs"><span class="line"><span style="color:#FFFFFF">public </span><span style="color:#A96BC0">interface</span><span style="color:#A96BC0"> IEpic</span><span style="color:#7B6995">&#x3C;</span><span style="color:#A96BC0">State</span><span style="color:#7B6995">></span></span>
<span class="line"><span style="color:#7B6995">{</span></span>
<span class="line"><span style="color:#FFFFFF">    public </span><span style="color:#FFFFFF">string</span><span style="color:#FFFFFF"> Name </span><span style="color:#7B6995">{</span><span style="color:#A96BC0"> get</span><span style="color:#7B6995">;</span><span style="color:#7B6995"> }</span></span>
<span class="line"></span>
<span class="line"><span style="color:#FFFFFF">    public </span><span style="color:#A96BC0">IObservable</span><span style="color:#7B6995">&#x3C;</span><span style="color:#A96BC0">RootAction</span><span style="color:#7B6995">></span><span style="color:#EB64B9"> Create</span><span style="color:#7B6995">(</span></span>
<span class="line"><span style="color:#A96BC0">        IObservable</span><span style="color:#7B6995">&#x3C;</span><span style="color:#A96BC0">RootAction</span><span style="color:#7B6995">></span><span style="color:#FFFFFF"> actions</span><span style="color:#7B6995">,</span></span>
<span class="line"><span style="color:#A96BC0">        IObservable</span><span style="color:#7B6995">&#x3C;</span><span style="color:#A96BC0">State</span><span style="color:#7B6995">></span><span style="color:#FFFFFF"> states</span><span style="color:#7B6995">,</span></span>
<span class="line"><span style="color:#A96BC0">        IScheduler</span><span style="color:#FFFFFF"> scheduler</span><span style="color:#7B6995">);</span></span>
<span class="line"><span style="color:#7B6995">}</span></span></code></pre>
<p>The <code>IScheduler</code> is explicitly provided to encourage testing with
<code>TestScheduler</code> and also because we found reasons at runtime to inject
specific schedulers without the Epic needing to be aware which.</p>
<p>The <code>Name</code> is useful for logging and debugging.</p>
<p>An example &quot;Cool Feature&quot; Epic might look something like:</p>
<pre class="shiki laserwave" style="background-color:#27212e;color:#ffffff" tabindex="0"><code class="language-cs"><span class="line"><span style="color:#FFFFFF">public </span><span style="color:#A96BC0">record</span><span style="color:#A96BC0"> CoolFeatureLoaded</span><span style="color:#7B6995">(</span><span style="color:#A96BC0">IEnumerable</span><span style="color:#7B6995">&#x3C;</span><span style="color:#A96BC0">CoolThing</span><span style="color:#7B6995">></span><span style="color:#FFFFFF"> CoolThings</span><span style="color:#7B6995">)</span></span>
<span class="line"><span style="color:#7B6995">    :</span><span style="color:#A96BC0"> CoolFeatureAction</span><span style="color:#7B6995">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#FFFFFF">public </span><span style="color:#A96BC0">class</span><span style="color:#A96BC0"> CoolFeatureLoadedReducer</span><span style="color:#7B6995"> :</span><span style="color:#A96BC0"> Reducer</span><span style="color:#7B6995">&#x3C;</span><span style="color:#A96BC0">CoolFeatureLoaded</span><span style="color:#7B6995">,</span><span style="color:#A96BC0"> CoolFeatureState</span><span style="color:#7B6995">></span></span>
<span class="line"><span style="color:#7B6995">{</span></span>
<span class="line"><span style="color:#FFFFFF">    public </span><span style="color:#A96BC0">CoolFeatureState</span><span style="color:#EB64B9"> Reduce</span><span style="color:#7B6995">(</span></span>
<span class="line"><span style="color:#A96BC0">        CoolFeatureLoaded</span><span style="color:#FFFFFF"> action</span><span style="color:#7B6995">,</span></span>
<span class="line"><span style="color:#A96BC0">        CoolFeatureState</span><span style="color:#FFFFFF"> state</span></span>
<span class="line"><span style="color:#7B6995">    )</span><span style="color:#74DFC4"> =></span></span>
<span class="line"><span style="color:#FFF">        state</span><span style="color:#74DFC4"> with</span><span style="color:#7B6995"> {</span><span style="color:#FFF"> CoolThings</span><span style="color:#74DFC4"> =</span><span style="color:#FFF"> action</span><span style="color:#74DFC4">.</span><span style="color:#FFF">CoolThings</span><span style="color:#7B6995"> };</span></span>
<span class="line"><span style="color:#7B6995">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#91889B">/// </span><span style="color:#7B6995">&#x3C;</span><span style="color:#74DFC4">summary</span><span style="color:#7B6995">></span></span>
<span class="line"><span style="color:#91889B">/// When Cool Feature has initialized, load any applicable CoolThings.</span></span>
<span class="line"><span style="color:#91889B">/// </span><span style="color:#7B6995">&#x3C;/</span><span style="color:#74DFC4">summary</span><span style="color:#7B6995">></span></span>
<span class="line"><span style="color:#FFFFFF">public </span><span style="color:#A96BC0">class</span><span style="color:#A96BC0"> CoolThingLoader</span><span style="color:#7B6995">(</span><span style="color:#A96BC0">ICoolThingService</span><span style="color:#FFFFFF"> service</span><span style="color:#7B6995">)</span></span>
<span class="line"><span style="color:#7B6995">    :</span><span style="color:#A96BC0"> IEpic</span><span style="color:#7B6995">&#x3C;</span><span style="color:#A96BC0">CoolFeatureState</span><span style="color:#7B6995">></span></span>
<span class="line"><span style="color:#7B6995">{</span></span>
<span class="line"><span style="color:#FFFFFF">    public </span><span style="color:#FFFFFF">string</span><span style="color:#FFFFFF"> Name </span><span style="color:#74DFC4">=></span><span style="color:#74DFC4"> nameof</span><span style="color:#7B6995">(</span><span style="color:#FFF">CoolThingLoader</span><span style="color:#7B6995">);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#FFFFFF">    public </span><span style="color:#A96BC0">IObservable</span><span style="color:#7B6995">&#x3C;</span><span style="color:#A96BC0">RootAction</span><span style="color:#7B6995">></span><span style="color:#EB64B9"> Create</span><span style="color:#7B6995">(</span></span>
<span class="line"><span style="color:#A96BC0">        IObservable</span><span style="color:#7B6995">&#x3C;</span><span style="color:#A96BC0">RootAction</span><span style="color:#7B6995">></span><span style="color:#FFFFFF"> actions</span><span style="color:#7B6995">,</span></span>
<span class="line"><span style="color:#A96BC0">        IObservable</span><span style="color:#7B6995">&#x3C;</span><span style="color:#A96BC0">CoolFeatureState</span><span style="color:#7B6995">></span><span style="color:#FFFFFF"> states</span><span style="color:#7B6995">,</span></span>
<span class="line"><span style="color:#A96BC0">        IScheduler</span><span style="color:#FFFFFF"> scheduler</span></span>
<span class="line"><span style="color:#7B6995">    )</span><span style="color:#74DFC4"> =></span></span>
<span class="line"><span style="color:#FFF">        actions</span><span style="color:#74DFC4">.</span><span style="color:#EB64B9">OfType</span><span style="color:#7B6995">&#x3C;</span><span style="color:#A96BC0">CoolFeatureInitialized</span><span style="color:#7B6995">>()</span></span>
<span class="line"><span style="color:#91889B">            // Sometimes a .WithLatestFrom() is useful here if the</span></span>
<span class="line"><span style="color:#91889B">            // reducer calculates something, say in this case the</span></span>
<span class="line"><span style="color:#91889B">            // API wants the calculated CoolStarted time. Actions are</span></span>
<span class="line"><span style="color:#91889B">            // the "transaction model" for the Store, so the state</span></span>
<span class="line"><span style="color:#91889B">            // after an Action is observed should always reflect all of</span></span>
<span class="line"><span style="color:#91889B">            // the Reducers have run.</span></span>
<span class="line"><span style="color:#74DFC4">            .</span><span style="color:#EB64B9">Select</span><span style="color:#7B6995">(</span><span style="color:#FFFFFF">action </span><span style="color:#74DFC4">=></span><span style="color:#FFF"> action</span><span style="color:#74DFC4">.</span><span style="color:#FFF">IsCool</span></span>
<span class="line"><span style="color:#74DFC4">                ?</span><span style="color:#FFF"> Observable</span><span style="color:#74DFC4">.</span><span style="color:#EB64B9">FromAsync</span><span style="color:#7B6995">(</span><span style="color:#FFFFFF">async </span><span style="color:#7B6995">(</span><span style="color:#FFFFFF">ct</span><span style="color:#7B6995">)</span><span style="color:#74DFC4"> =></span></span>
<span class="line"><span style="color:#74DFC4">                    new</span><span style="color:#A96BC0"> CoolFeatureLoaded</span><span style="color:#7B6995">(</span></span>
<span class="line"><span style="color:#74DFC4">                        await</span><span style="color:#FFF"> service</span><span style="color:#74DFC4">.</span><span style="color:#EB64B9">LoadCoolThings</span><span style="color:#7B6995">(</span><span style="color:#FFF">ct</span><span style="color:#7B6995">)),</span></span>
<span class="line"><span style="color:#FFF">                    scheduler</span><span style="color:#7B6995">)</span></span>
<span class="line"><span style="color:#74DFC4">                :</span><span style="color:#FFF"> Observable</span><span style="color:#74DFC4">.</span><span style="color:#EB64B9">Return</span><span style="color:#7B6995">(</span><span style="color:#74DFC4">new</span><span style="color:#A96BC0"> CoolFeatureLoaded</span><span style="color:#7B6995">([])))</span></span>
<span class="line"><span style="color:#74DFC4">            .</span><span style="color:#EB64B9">Switch</span><span style="color:#7B6995">();</span></span>
<span class="line"><span style="color:#7B6995">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#91889B">// updated registration</span></span>
<span class="line"><span style="color:#FFFFFF">public static </span><span style="color:#A96BC0">class</span><span style="color:#A96BC0"> CoolFeatureExtensions</span></span>
<span class="line"><span style="color:#7B6995">{</span></span>
<span class="line"><span style="color:#FFFFFF">    public static </span><span style="color:#A96BC0">IServiceCollection</span><span style="color:#EB64B9"> AddCoolFeature</span><span style="color:#7B6995">(</span><span style="color:#FFFFFF">this </span><span style="color:#A96BC0">IServiceCollection</span><span style="color:#FFFFFF"> services</span><span style="color:#7B6995">)</span><span style="color:#74DFC4"> =></span><span style="color:#FFF"> services</span></span>
<span class="line"><span style="color:#74DFC4">        .</span><span style="color:#EB64B9">AddTransient</span><span style="color:#7B6995">&#x3C;</span><span style="color:#A96BC0">IReducer</span><span style="color:#7B6995">&#x3C;</span><span style="color:#A96BC0">CoolFeatureState</span><span style="color:#7B6995">>,</span><span style="color:#A96BC0"> CoolFeatureInitializedReducer</span><span style="color:#7B6995">>()</span></span>
<span class="line"><span style="color:#74DFC4">        .</span><span style="color:#EB64B9">AddTransient</span><span style="color:#7B6995">&#x3C;</span><span style="color:#A96BC0">IReducer</span><span style="color:#7B6995">&#x3C;</span><span style="color:#A96BC0">CoolFeatureState</span><span style="color:#7B6995">>,</span><span style="color:#A96BC0"> CoolFeatureLoadedReducer</span><span style="color:#7B6995">>()</span></span>
<span class="line"><span style="color:#74DFC4">        .</span><span style="color:#EB64B9">AddTransient</span><span style="color:#7B6995">&#x3C;</span><span style="color:#A96BC0">IEpic</span><span style="color:#7B6995">&#x3C;</span><span style="color:#A96BC0">CoolFeatureState</span><span style="color:#7B6995">>,</span><span style="color:#A96BC0"> CoolThingLoader</span><span style="color:#7B6995">>()</span></span>
<span class="line"><span style="color:#74DFC4">        .</span><span style="color:#EB64B9">AddSlice</span><span style="color:#7B6995">(</span><span style="color:#FFF">RootState</span><span style="color:#74DFC4">.</span><span style="color:#FFF">CoolFeatureLens</span><span style="color:#7B6995">);</span></span>
<span class="line"><span style="color:#7B6995">}</span></span></code></pre>
<h2>Responsibility Digression about Epics</h2>
<p>One thing that should be obvious from the Epic interface is that an Epic
may return the same Action it observes in the first place. This is a
recipe for an infinite loop.</p>
<p>The key wisdom when writing an Epic is that often attributed to
Spider-Man's Uncle Ben:</p>
<blockquote><p>With great power comes great responsibility.</p>
</blockquote><p>Loops are a key flow control power in software development. Just as you
might use a <code>while</code> loop, you may find needs for Action loops in your
Epics. Just like working with a <code>while</code> loop, you need to make sure that
your loop condition is solid enough to avoid infinite loops, and you want
to make sure that your loop has all the other fast exits it may need.</p>
<h1><code>EpicHost&lt;State&gt;</code></h1>
<p>The last big piece of pattern is initializing all the Epics at the right
point in application startup. This effort is left to a simple class
known as the <code>EpicHost&lt;State&gt;</code>.</p>
<pre class="shiki laserwave" style="background-color:#27212e;color:#ffffff" tabindex="0"><code class="language-cs"><span class="line"><span style="color:#FFFFFF">public </span><span style="color:#A96BC0">interface</span><span style="color:#A96BC0"> IEpicHost</span></span>
<span class="line"><span style="color:#7B6995">{</span></span>
<span class="line"><span style="color:#FFFFFF">    public void </span><span style="color:#EB64B9">Initialize</span><span style="color:#7B6995">();</span></span>
<span class="line"><span style="color:#FFFFFF">    public </span><span style="color:#A96BC0">IEnumerable</span><span style="color:#7B6995">&#x3C;</span><span style="color:#FFFFFF">string</span><span style="color:#7B6995">></span><span style="color:#FFFFFF"> EpicNames </span><span style="color:#7B6995">{</span><span style="color:#A96BC0"> get</span><span style="color:#7B6995">;</span><span style="color:#7B6995"> }</span></span>
<span class="line"><span style="color:#7B6995">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#FFFFFF">public </span><span style="color:#A96BC0">class</span><span style="color:#A96BC0"> EpicHost</span><span style="color:#7B6995">&#x3C;</span><span style="color:#A96BC0">State</span><span style="color:#7B6995">>(</span></span>
<span class="line"><span style="color:#A96BC0">    ILogger</span><span style="color:#7B6995">&#x3C;</span><span style="color:#A96BC0">EpicHost</span><span style="color:#7B6995">&#x3C;</span><span style="color:#A96BC0">State</span><span style="color:#7B6995">>></span><span style="color:#FFFFFF"> logger</span><span style="color:#7B6995">,</span></span>
<span class="line"><span style="color:#A96BC0">    IEnumerable</span><span style="color:#7B6995">&#x3C;</span><span style="color:#A96BC0">IEpic</span><span style="color:#7B6995">&#x3C;</span><span style="color:#A96BC0">State</span><span style="color:#7B6995">>></span><span style="color:#FFFFFF"> epics</span><span style="color:#7B6995">,</span></span>
<span class="line"><span style="color:#A96BC0">    IAction</span><span style="color:#FFFFFF"> action</span><span style="color:#7B6995">,</span></span>
<span class="line"><span style="color:#A96BC0">    IDispatch</span><span style="color:#FFFFFF"> dispatch</span><span style="color:#7B6995">,</span></span>
<span class="line"><span style="color:#A96BC0">    ISlice</span><span style="color:#7B6995">&#x3C;</span><span style="color:#A96BC0">State</span><span style="color:#7B6995">></span><span style="color:#FFFFFF"> state</span><span style="color:#7B6995">)</span></span>
<span class="line"><span style="color:#7B6995">    :</span><span style="color:#A96BC0"> IEpicHost</span><span style="color:#7B6995">,</span><span style="color:#A96BC0"> IDisposable</span></span>
<span class="line"><span style="color:#7B6995">{</span></span>
<span class="line"><span style="color:#FFFFFF">    private bool initialized</span><span style="color:#7B6995">;</span></span>
<span class="line"><span style="color:#FFFFFF">    private </span><span style="color:#A96BC0">ISubscription</span><span style="color:#7B6995">?</span><span style="color:#FFFFFF"> subscription</span><span style="color:#7B6995">;</span></span>
<span class="line"><span style="color:#FFFFFF">    private </span><span style="color:#A96BC0">IScheduler</span><span style="color:#FFFFFF"> scheduler </span><span style="color:#74DFC4">=</span><span style="color:#FFF"> Scheduler</span><span style="color:#74DFC4">.</span><span style="color:#FFF">Default</span><span style="color:#7B6995">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#FFFFFF">    public </span><span style="color:#A96BC0">IEnumerable</span><span style="color:#7B6995">&#x3C;</span><span style="color:#FFFFFF">string</span><span style="color:#7B6995">></span><span style="color:#FFFFFF"> EpicNames </span><span style="color:#74DFC4">=></span><span style="color:#FFF"> epics</span><span style="color:#74DFC4">.</span><span style="color:#EB64B9">Select</span><span style="color:#7B6995">(</span><span style="color:#FFFFFF">epic </span><span style="color:#74DFC4">=></span><span style="color:#FFF"> epic</span><span style="color:#74DFC4">.</span><span style="color:#FFF">Name</span><span style="color:#7B6995">);</span></span>
<span class="line"><span style="color:#FFFFFF">    </span></span>
<span class="line"><span style="color:#FFFFFF">    private </span><span style="color:#A96BC0">IObservable</span><span style="color:#7B6995">&#x3C;</span><span style="color:#A96BC0">RootAction</span><span style="color:#7B6995">></span><span style="color:#EB64B9"> CreateEpic</span><span style="color:#7B6995">(</span><span style="color:#A96BC0">IEpic</span><span style="color:#7B6995">&#x3C;</span><span style="color:#A96BC0">State</span><span style="color:#7B6995">></span><span style="color:#FFFFFF"> epic</span><span style="color:#7B6995">)</span></span>
<span class="line"><span style="color:#7B6995">    {</span></span>
<span class="line"><span style="color:#FFF">        logger</span><span style="color:#74DFC4">.</span><span style="color:#EB64B9">LogDebug</span><span style="color:#7B6995">(</span><span style="color:#7B6995">"</span><span style="color:#B4DCE7">Epic {Epic} starting in {Host}</span><span style="color:#7B6995">"</span><span style="color:#7B6995">,</span><span style="color:#FFF"> epic</span><span style="color:#74DFC4">.</span><span style="color:#FFF">Name</span><span style="color:#7B6995">,</span><span style="color:#EB64B9"> GetType</span><span style="color:#7B6995">()</span><span style="color:#74DFC4">.</span><span style="color:#FFF">Name</span><span style="color:#7B6995">);</span></span>
<span class="line"><span style="color:#40B4C4">        return</span><span style="color:#FFF"> epic</span><span style="color:#74DFC4">.</span><span style="color:#EB64B9">Create</span><span style="color:#7B6995">(</span><span style="color:#FFF">action</span><span style="color:#74DFC4">.</span><span style="color:#FFF">Actions</span><span style="color:#7B6995">,</span><span style="color:#FFF"> state</span><span style="color:#74DFC4">.</span><span style="color:#FFF">States</span><span style="color:#7B6995">,</span><span style="color:#FFF"> scheduler</span><span style="color:#7B6995">)</span></span>
<span class="line"><span style="color:#74DFC4">            .</span><span style="color:#EB64B9">Catch</span><span style="color:#7B6995">(</span><span style="color:#FFFFFF">error </span><span style="color:#74DFC4">=></span></span>
<span class="line"><span style="color:#7B6995">            {</span></span>
<span class="line"><span style="color:#FFF">                logger</span><span style="color:#74DFC4">.</span><span style="color:#EB64B9">LogError</span><span style="color:#7B6995">(</span><span style="color:#FFF">error</span><span style="color:#7B6995">,</span><span style="color:#7B6995"> "</span><span style="color:#B4DCE7">Error in epic {Epic}</span><span style="color:#7B6995">"</span><span style="color:#7B6995">,</span><span style="color:#FFF"> epic</span><span style="color:#74DFC4">.</span><span style="color:#FFF">Name</span><span style="color:#7B6995">);</span></span>
<span class="line"><span style="color:#91889B">                // Restart:</span></span>
<span class="line"><span style="color:#40B4C4">                return</span><span style="color:#EB64B9"> CreateEpic</span><span style="color:#7B6995">(</span><span style="color:#FFF">epic</span><span style="color:#7B6995">);</span></span>
<span class="line"><span style="color:#91889B">                // NOTE: You might want to include a backoff strategy for</span></span>
<span class="line"><span style="color:#91889B">                // restarts or other additional debugging tools here.</span></span>
<span class="line"><span style="color:#7B6995">            });</span></span>
<span class="line"><span style="color:#7B6995">    }</span></span>
<span class="line"></span>
<span class="line"><span style="color:#FFFFFF">    private </span><span style="color:#A96BC0">IObservable</span><span style="color:#7B6995">&#x3C;</span><span style="color:#A96BC0">RootAction</span><span style="color:#7B6995">></span><span style="color:#EB64B9"> CreateEpics</span><span style="color:#7B6995">()</span><span style="color:#74DFC4"> =></span></span>
<span class="line"><span style="color:#FFF">        Observable</span><span style="color:#74DFC4">.</span><span style="color:#EB64B9">Merge</span><span style="color:#7B6995">(</span><span style="color:#FFF">epics</span><span style="color:#74DFC4">.</span><span style="color:#EB64B9">Select</span><span style="color:#7B6995">(</span><span style="color:#FFF">CreateEpic</span><span style="color:#7B6995">));</span></span>
<span class="line"></span>
<span class="line"><span style="color:#FFFFFF">    public void </span><span style="color:#EB64B9">Initialize</span><span style="color:#7B6995">()</span></span>
<span class="line"><span style="color:#7B6995">    {</span></span>
<span class="line"><span style="color:#40B4C4">        if</span><span style="color:#7B6995"> (</span><span style="color:#FFF">initialized</span><span style="color:#7B6995">)</span></span>
<span class="line"><span style="color:#7B6995">        {</span></span>
<span class="line"><span style="color:#40B4C4">            return</span><span style="color:#7B6995">;</span></span>
<span class="line"><span style="color:#7B6995">        }</span></span>
<span class="line"><span style="color:#FFF">        initialized</span><span style="color:#74DFC4"> =</span><span style="color:#FFE261"> true</span><span style="color:#7B6995">;</span></span>
<span class="line"><span style="color:#FFF">        subscription</span><span style="color:#74DFC4"> =</span><span style="color:#EB64B9"> CreateEpics</span><span style="color:#7B6995">()</span></span>
<span class="line"><span style="color:#74DFC4">            .</span><span style="color:#EB64B9">Subscribe</span><span style="color:#7B6995">(</span></span>
<span class="line"><span style="color:#FFFFFF">                action </span><span style="color:#74DFC4">=></span><span style="color:#FFF"> dispatch</span><span style="color:#74DFC4">.</span><span style="color:#EB64B9">Dispatch</span><span style="color:#7B6995">(</span><span style="color:#FFF">action</span><span style="color:#7B6995">),</span></span>
<span class="line"><span style="color:#FFFFFF">                error </span><span style="color:#74DFC4">=></span><span style="color:#FFF"> logger</span><span style="color:#74DFC4">.</span><span style="color:#EB64B9">LogError</span><span style="color:#7B6995">(</span><span style="color:#FFF">error</span><span style="color:#7B6995">,</span><span style="color:#7B6995"> "</span><span style="color:#B4DCE7">Error in {Host}</span><span style="color:#7B6995">"</span><span style="color:#7B6995">,</span><span style="color:#EB64B9"> GetType</span><span style="color:#7B6995">()</span><span style="color:#74DFC4">.</span><span style="color:#FFF">Name</span><span style="color:#7B6995">),</span></span>
<span class="line"><span style="color:#7B6995">                ()</span><span style="color:#74DFC4"> =></span><span style="color:#FFF"> logger</span><span style="color:#74DFC4">.</span><span style="color:#EB64B9">LogError</span><span style="color:#7B6995">(</span><span style="color:#7B6995">"</span><span style="color:#B4DCE7">{Host} completed</span><span style="color:#7B6995">"</span><span style="color:#7B6995">,</span><span style="color:#EB64B9"> GetType</span><span style="color:#7B6995">()</span><span style="color:#74DFC4">.</span><span style="color:#FFF">Name</span><span style="color:#7B6995">),</span></span>
<span class="line"><span style="color:#7B6995">            );</span></span>
<span class="line"><span style="color:#7B6995">    }</span></span>
<span class="line"></span>
<span class="line"><span style="color:#FFFFFF">    public void </span><span style="color:#EB64B9">Dispose</span><span style="color:#7B6995">()</span></span>
<span class="line"><span style="color:#7B6995">    {</span></span>
<span class="line"><span style="color:#FFF">        GC</span><span style="color:#74DFC4">.</span><span style="color:#EB64B9">SuppressFinalize</span><span style="color:#7B6995">(</span><span style="color:#FFE261">this</span><span style="color:#7B6995">);</span></span>
<span class="line"><span style="color:#FFF">        subscription</span><span style="color:#74DFC4">?.</span><span style="color:#EB64B9">Dispose</span><span style="color:#7B6995">();</span></span>
<span class="line"><span style="color:#7B6995">    }</span></span>
<span class="line"><span style="color:#7B6995">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#FFFFFF">public static </span><span style="color:#A96BC0">class</span><span style="color:#A96BC0"> EpicHostExtensions</span></span>
<span class="line"><span style="color:#7B6995">{</span></span>
<span class="line"><span style="color:#FFFFFF">    public static </span><span style="color:#A96BC0">IServiceCollection</span><span style="color:#EB64B9"> AddEpicHost</span><span style="color:#7B6995">&#x3C;</span><span style="color:#A96BC0">State</span><span style="color:#7B6995">>(</span></span>
<span class="line"><span style="color:#FFFFFF">        this </span><span style="color:#A96BC0">IServiceCollection</span><span style="color:#FFFFFF"> services</span></span>
<span class="line"><span style="color:#7B6995">    )</span><span style="color:#74DFC4"> =></span><span style="color:#FFF"> services</span></span>
<span class="line"><span style="color:#74DFC4">        .</span><span style="color:#EB64B9">AddSingleton</span><span style="color:#7B6995">&#x3C;</span><span style="color:#A96BC0">IEpicHost</span><span style="color:#7B6995">,</span><span style="color:#A96BC0"> EpicHost</span><span style="color:#7B6995">&#x3C;</span><span style="color:#A96BC0">State</span><span style="color:#7B6995">>>();</span></span>
<span class="line"><span style="color:#7B6995">}</span></span></code></pre>
<p><code>EpicNames</code> can be handy for debugging and testing.</p>
<p>Somewhere in your application startup path where it makes the most sense:</p>
<pre class="shiki laserwave" style="background-color:#27212e;color:#ffffff" tabindex="0"><code class="language-cs"><span class="line"><span style="color:#A96BC0">var</span><span style="color:#FFFFFF"> epicHosts </span><span style="color:#74DFC4">=</span><span style="color:#FFF"> services</span><span style="color:#74DFC4">.</span><span style="color:#EB64B9">GetServices</span><span style="color:#7B6995">&#x3C;</span><span style="color:#A96BC0">IEpicHost</span><span style="color:#7B6995">>();</span><span style="color:#91889B"> // or constructor injection</span></span>
<span class="line"><span style="color:#40B4C4">foreach</span><span style="color:#7B6995"> (</span><span style="color:#A96BC0">var</span><span style="color:#FFFFFF"> host </span><span style="color:#40B4C4">in</span><span style="color:#FFF"> epicHosts</span><span style="color:#7B6995">)</span></span>
<span class="line"><span style="color:#7B6995">{</span></span>
<span class="line"><span style="color:#FFF">    host</span><span style="color:#74DFC4">.</span><span style="color:#EB64B9">Initialize</span><span style="color:#7B6995">();</span></span>
<span class="line"><span style="color:#7B6995">}</span></span></code></pre>
<p>To finish up our Cool Feature example registration, it's final shape is:</p>
<pre class="shiki laserwave" style="background-color:#27212e;color:#ffffff" tabindex="0"><code class="language-cs"><span class="line"><span style="color:#FFFFFF">public static </span><span style="color:#A96BC0">class</span><span style="color:#A96BC0"> CoolFeatureExtensions</span></span>
<span class="line"><span style="color:#7B6995">{</span></span>
<span class="line"><span style="color:#FFFFFF">    public static </span><span style="color:#A96BC0">IServiceCollection</span><span style="color:#EB64B9"> AddCoolFeature</span><span style="color:#7B6995">(</span><span style="color:#FFFFFF">this </span><span style="color:#A96BC0">IServiceCollection</span><span style="color:#FFFFFF"> services</span><span style="color:#7B6995">)</span><span style="color:#74DFC4"> =></span><span style="color:#FFF"> services</span></span>
<span class="line"><span style="color:#74DFC4">        .</span><span style="color:#EB64B9">AddTransient</span><span style="color:#7B6995">&#x3C;</span><span style="color:#A96BC0">IReducer</span><span style="color:#7B6995">&#x3C;</span><span style="color:#A96BC0">CoolFeatureState</span><span style="color:#7B6995">>,</span><span style="color:#A96BC0"> CoolFeatureInitializedReducer</span><span style="color:#7B6995">>()</span></span>
<span class="line"><span style="color:#74DFC4">        .</span><span style="color:#EB64B9">AddTransient</span><span style="color:#7B6995">&#x3C;</span><span style="color:#A96BC0">IReducer</span><span style="color:#7B6995">&#x3C;</span><span style="color:#A96BC0">CoolFeatureState</span><span style="color:#7B6995">>,</span><span style="color:#A96BC0"> CoolFeatureLoadedReducer</span><span style="color:#7B6995">>()</span></span>
<span class="line"><span style="color:#74DFC4">        .</span><span style="color:#EB64B9">AddTransient</span><span style="color:#7B6995">&#x3C;</span><span style="color:#A96BC0">IEpic</span><span style="color:#7B6995">&#x3C;</span><span style="color:#A96BC0">CoolFeatureState</span><span style="color:#7B6995">>,</span><span style="color:#A96BC0"> CoolThingLoader</span><span style="color:#7B6995">>()</span></span>
<span class="line"><span style="color:#74DFC4">        .</span><span style="color:#EB64B9">AddSlice</span><span style="color:#7B6995">(</span><span style="color:#FFF">RootState</span><span style="color:#74DFC4">.</span><span style="color:#FFF">CoolFeatureLens</span><span style="color:#7B6995">)</span></span>
<span class="line"><span style="color:#74DFC4">        .</span><span style="color:#EB64B9">AddEpicHost</span><span style="color:#7B6995">&#x3C;</span><span style="color:#A96BC0">CoolFeatureState</span><span style="color:#7B6995">>();</span></span>
<span class="line"><span style="color:#7B6995">}</span></span></code></pre>
<p>(You might want to inject an Epic Host for the <code>RootState</code> as well.)</p>
<h1>Bonus: DI Registration Tests</h1>
<p>I found it useful to test that all Reducers and Epics were registered
in DI to avoid forgetting to register one (and then being confused why
it wasn't working).</p>
<p>It needs this helper function for Reflection:</p>
<pre class="shiki laserwave" style="background-color:#27212e;color:#ffffff" tabindex="0"><code class="language-cs"><span class="line"><span style="color:#FFFFFF">public </span><span style="color:#A96BC0">Func</span><span style="color:#7B6995">&#x3C;</span><span style="color:#A96BC0">Type</span><span style="color:#7B6995">,</span><span style="color:#FFFFFF"> bool</span><span style="color:#7B6995">></span><span style="color:#EB64B9"> ImplementsGenericInterface</span><span style="color:#7B6995">(</span><span style="color:#A96BC0">Type</span><span style="color:#FFFFFF"> interface</span><span style="color:#7B6995">)</span></span>
<span class="line"><span style="color:#7B6995">{</span></span>
<span class="line"><span style="color:#A96BC0">    var</span><span style="color:#FFFFFF"> genericInterface </span><span style="color:#74DFC4">=</span><span style="color:#FFF"> interface</span><span style="color:#74DFC4">.</span><span style="color:#EB64B9">GetGenericTypeDefinition</span><span style="color:#7B6995">();</span></span>
<span class="line"><span style="color:#40B4C4">    return</span><span style="color:#7B6995"> (</span><span style="color:#A96BC0">Type</span><span style="color:#FFFFFF"> targetType</span><span style="color:#7B6995">)</span><span style="color:#74DFC4"> =></span><span style="color:#FFF"> targetType</span></span>
<span class="line"><span style="color:#74DFC4">        .</span><span style="color:#EB64B9">GetInterfaces</span><span style="color:#7B6995">()</span></span>
<span class="line"><span style="color:#74DFC4">        .</span><span style="color:#EB64B9">Any</span><span style="color:#7B6995">(</span><span style="color:#FFFFFF">x </span><span style="color:#74DFC4">=></span><span style="color:#FFF"> x</span><span style="color:#74DFC4">.</span><span style="color:#FFF">IsGenericInterface</span><span style="color:#74DFC4"> &#x26;&#x26;</span><span style="color:#FFF"> x</span><span style="color:#74DFC4">.</span><span style="color:#EB64B9">GetGenericTypeDefinition</span><span style="color:#7B6995">()</span><span style="color:#74DFC4"> ==</span><span style="color:#FFF"> genericInterface</span></span>
<span class="line"><span style="color:#74DFC4">            ||</span><span style="color:#FFF"> x</span><span style="color:#74DFC4">.</span><span style="color:#EB64B9">GetInterfaces</span><span style="color:#7B6995">()</span><span style="color:#74DFC4">.</span><span style="color:#EB64B9">Where</span><span style="color:#7B6995">(</span><span style="color:#FFFFFF">i </span><span style="color:#74DFC4">=></span><span style="color:#FFF"> i</span><span style="color:#74DFC4">.</span><span style="color:#FFF">IsGenericInterface</span><span style="color:#7B6995">)</span><span style="color:#74DFC4">.</span><span style="color:#EB64B9">Select</span><span style="color:#7B6995">(</span><span style="color:#FFFFFF">i </span><span style="color:#74DFC4">=></span><span style="color:#FFF"> i</span><span style="color:#74DFC4">.</span><span style="color:#EB64B9">GetGenericTypeDefinition</span><span style="color:#7B6995">())</span><span style="color:#74DFC4">.</span><span style="color:#EB64B9">Contains</span><span style="color:#7B6995">(</span><span style="color:#FFF">genericInterface</span><span style="color:#7B6995">));</span></span>
<span class="line"><span style="color:#7B6995">}</span></span></code></pre>
<p>With that in hand in a test context where you can test your appropriately
registered DI container, you should be able to use tests that look
something like (modulo your test framework and assertion framework
differences):</p>
<pre class="shiki laserwave" style="background-color:#27212e;color:#ffffff" tabindex="0"><code class="language-cs"><span class="line"><span style="color:#7B6995">[</span><span style="color:#A96BC0">Fact</span><span style="color:#7B6995">]</span></span>
<span class="line"><span style="color:#FFFFFF">public void </span><span style="color:#EB64B9">ReducersAreRegistered</span><span style="color:#7B6995">()</span></span>
<span class="line"><span style="color:#7B6995">{</span></span>
<span class="line"><span style="color:#A96BC0">    var</span><span style="color:#FFFFFF"> isReducer </span><span style="color:#74DFC4">=</span><span style="color:#EB64B9"> ImplementsGenericInterface</span><span style="color:#7B6995">(</span><span style="color:#74DFC4">typeof</span><span style="color:#7B6995">(</span><span style="color:#A96BC0">IReducer</span><span style="color:#7B6995">&#x3C;>));</span></span>
<span class="line"><span style="color:#A96BC0">    var</span><span style="color:#FFFFFF"> reducers </span><span style="color:#74DFC4">=</span><span style="color:#74DFC4"> typeof</span><span style="color:#7B6995">(</span><span style="color:#A96BC0">Store</span><span style="color:#7B6995">)</span><span style="color:#74DFC4">.</span><span style="color:#FFF">Assembly</span><span style="color:#74DFC4">.</span><span style="color:#EB64B9">GetTypes</span><span style="color:#7B6995">()</span></span>
<span class="line"><span style="color:#74DFC4">        .</span><span style="color:#EB64B9">Where</span><span style="color:#7B6995">(</span><span style="color:#FFFFFF">t </span><span style="color:#74DFC4">=></span><span style="color:#74DFC4"> !</span><span style="color:#FFF">t</span><span style="color:#74DFC4">.</span><span style="color:#FFF">IsAbstract</span><span style="color:#74DFC4"> &#x26;&#x26;</span><span style="color:#FFF"> t</span><span style="color:#74DFC4"> !=</span><span style="color:#74DFC4"> typeof</span><span style="color:#7B6995">(</span><span style="color:#A96BC0">Slice</span><span style="color:#7B6995">&#x3C;>)</span><span style="color:#74DFC4"> &#x26;&#x26;</span><span style="color:#EB64B9"> isReducer</span><span style="color:#7B6995">(</span><span style="color:#FFF">t</span><span style="color:#7B6995">));</span></span>
<span class="line"><span style="color:#40B4C4">    foreach</span><span style="color:#7B6995"> (</span><span style="color:#A96BC0">var</span><span style="color:#FFFFFF"> reducer </span><span style="color:#40B4C4">in</span><span style="color:#FFF"> reducers</span><span style="color:#7B6995">)</span></span>
<span class="line"><span style="color:#7B6995">    {</span></span>
<span class="line"><span style="color:#FFF">        Assert</span><span style="color:#74DFC4">.</span><span style="color:#EB64B9">IsTrue</span><span style="color:#7B6995">(</span></span>
<span class="line"><span style="color:#FFF">            Services</span><span style="color:#74DFC4">.</span><span style="color:#EB64B9">Any</span><span style="color:#7B6995">(</span><span style="color:#FFFFFF">desc </span><span style="color:#74DFC4">=></span><span style="color:#FFF"> desc</span><span style="color:#74DFC4">.</span><span style="color:#FFF">ImplementationType</span><span style="color:#74DFC4"> ==</span><span style="color:#FFF"> reducer</span><span style="color:#7B6995">),</span></span>
<span class="line"><span style="color:#7B6995">            $"</span><span style="color:#B4DCE7">Reducer </span><span style="color:#7B6995">{</span><span style="color:#FFF">reducer</span><span style="color:#74DFC4">.</span><span style="color:#FFF">FullName</span><span style="color:#7B6995">}</span><span style="color:#B4DCE7"> should be registered</span><span style="color:#7B6995">"</span><span style="color:#7B6995">);</span></span>
<span class="line"><span style="color:#7B6995">    }</span></span>
<span class="line"><span style="color:#7B6995">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#7B6995">[</span><span style="color:#A96BC0">Fact</span><span style="color:#7B6995">]</span></span>
<span class="line"><span style="color:#FFFFFF">public void </span><span style="color:#EB64B9">EpicsAreRegistered</span><span style="color:#7B6995">()</span></span>
<span class="line"><span style="color:#7B6995">{</span></span>
<span class="line"><span style="color:#A96BC0">    var</span><span style="color:#FFFFFF"> isEpic </span><span style="color:#74DFC4">=</span><span style="color:#EB64B9"> ImplementsGenericInterface</span><span style="color:#7B6995">(</span><span style="color:#74DFC4">typeof</span><span style="color:#7B6995">(</span><span style="color:#A96BC0">IEpic</span><span style="color:#7B6995">&#x3C;>));</span></span>
<span class="line"><span style="color:#A96BC0">    var</span><span style="color:#FFFFFF"> epics </span><span style="color:#74DFC4">=</span><span style="color:#74DFC4"> typeof</span><span style="color:#7B6995">(</span><span style="color:#A96BC0">Store</span><span style="color:#7B6995">)</span><span style="color:#74DFC4">.</span><span style="color:#FFF">Assembly</span><span style="color:#74DFC4">.</span><span style="color:#EB64B9">GetTypes</span><span style="color:#7B6995">()</span></span>
<span class="line"><span style="color:#74DFC4">        .</span><span style="color:#EB64B9">Where</span><span style="color:#7B6995">(</span><span style="color:#FFFFFF">t </span><span style="color:#74DFC4">=></span><span style="color:#74DFC4"> !</span><span style="color:#FFF">t</span><span style="color:#74DFC4">.</span><span style="color:#FFF">IsAbstract</span><span style="color:#74DFC4"> &#x26;&#x26;</span><span style="color:#EB64B9"> isEpic</span><span style="color:#7B6995">(</span><span style="color:#FFF">t</span><span style="color:#7B6995">));</span></span>
<span class="line"><span style="color:#40B4C4">    foreach</span><span style="color:#7B6995"> (</span><span style="color:#A96BC0">var</span><span style="color:#FFFFFF"> epic </span><span style="color:#40B4C4">in</span><span style="color:#FFF"> epics</span><span style="color:#7B6995">)</span></span>
<span class="line"><span style="color:#7B6995">    {</span></span>
<span class="line"><span style="color:#FFF">        Assert</span><span style="color:#74DFC4">.</span><span style="color:#EB64B9">IsTrue</span><span style="color:#7B6995">(</span></span>
<span class="line"><span style="color:#FFF">            Services</span><span style="color:#74DFC4">.</span><span style="color:#EB64B9">Any</span><span style="color:#7B6995">(</span><span style="color:#FFFFFF">desc </span><span style="color:#74DFC4">=></span><span style="color:#FFF"> desc</span><span style="color:#74DFC4">.</span><span style="color:#FFF">ImplementationType</span><span style="color:#74DFC4"> ==</span><span style="color:#FFF"> epic</span><span style="color:#7B6995">),</span></span>
<span class="line"><span style="color:#7B6995">            $"</span><span style="color:#B4DCE7">Epic </span><span style="color:#7B6995">{</span><span style="color:#FFF">epic</span><span style="color:#74DFC4">.</span><span style="color:#FFF">FullName</span><span style="color:#7B6995">}</span><span style="color:#B4DCE7"> should be registered</span><span style="color:#7B6995">"</span><span style="color:#7B6995">);</span></span>
<span class="line"><span style="color:#7B6995">    }</span></span>
<span class="line"><span style="color:#7B6995">}</span></span></code></pre>
<h1>A Pattern for Redux with Epics in C# Dependency Injection</h1>
<p>I think this pattern should feel built out of a lot of simple parts.
I feel like most of the work in this particular pattern is just the DI
&quot;glue&quot; to connect all the small lego parts of the pattern together.</p>
<p>I've had good success with the Epic Pattern for Redux in JS and I think
we saw some good use of this pattern in the C# project we were using this
pattern for (the view states and much of the business logic of a frontend
built in Blazor WASM).</p>
<p>The reliance of C# Dependency Injection in the way this pattern uses it
I think helps keep individual pieces small, focused, and easy to unit
test. I wrote a lot of Reducers and Epics in this pattern, with high test
coverage, and I appreciated it.</p>
<p>Given how much of the pattern seems to me to be most &quot;DI glue&quot; and
I couldn't see enough of a case to convert it to a C# library, I thought
it may still be useful to at least blog the pattern for posterity, as it
may be useful to other projects. In so far as this is copy and pasteable
code rather than an uncopyrightable generic description of a software
pattern, I believe the MIT license applies (no warranty, especially).</p>
]]>
      </content:encoded>
      <category>coding</category>
      <pubDate>Tue, 07 Apr 2026 00:00:00 GMT</pubDate>
    </item>
    <item>
      <title>Tiny Little Game Developer Paychecks</title>
      <link>https://blog.worldmaker.net/2025/11/23/tiny-paychecks/</link>
      <guid isPermaLink="false">https://blog.worldmaker.net/2025/11/23/tiny-paychecks/</guid>
      <content:encoded>
        <![CDATA[<p>About a year ago, I put together a small add-on for Fantasy Grounds to
add a <a href="https://forge.fantasygrounds.com/shop/items/1836/view">metric calendar</a> (or Stardate calendar) to the extensible calendar
system of the virtual tabletop (VTT) <a href="https://www.fantasygrounds.com/">Fantasy Grounds</a> because I was
contemplating an attempt to GM a one-off session, maybe two, for &quot;Star
Trek Adventures&quot; and thought it would be nice to include Stardate logs
in the campaign's calendar.</p>
<p>The <a href="https://github.com/WorldMaker/fg-stardate">metric calendar addon is open source</a> and you can pick up a
copy for free from its releases, or build it yourself with the attached
build script, if you want to save a (literal) quarter or see what add-on
development looks like for Fantasy Grounds.</p>
<p>I put it on the Fantasy Grounds Forge for a quarter, and out of every
quarter spent on it I get roughly 15 cents. After about a year and change
of it being on the Forge I can safely say that I've made &quot;candy bar&quot; money
from sales of it. It amuses me each time one of these payments arrives,
they truly are tiny, but it also makes me happy that scratching my own
little itch resulted in enough side income pay to buy a whole candy bar.</p>
<p>After many decades of failed or unfinished projects, I suppose that I'm
officially now a &quot;professional game developer&quot; having received candy bar
money for at least one effort. (That also amuses me about this.)</p>
<p>As far as I can tell I'm one of the few Fantasy Grounds add-on developers
using source control and source control automation, so I think I've now
promised at least a small article/guide on doing that, to help that
community take some of their efforts to the next level. I would like to
do that for them.</p>
<p>Also, if you haven't yet tried Fantasy Grounds yet, I can recommend it
as a nice VTT for many game systems. Just recently
<a href="https://www.youtube.com/watch?v=o27nuFjwgTM">Fantasy Grounds became free-to-play</a> (YouTube announcement video),
focusing on a business model of selling the books for game systems over
the software itself. I think it's got some advantages over other VTTs
because it is &quot;offline-first&quot; rather than &quot;cloud-native&quot;. It's
Unity-based game client copies everything needed to play locally and a
GM can use it offline or even allow play over a LAN disconnected from
the internet (as long as everyone's client versions are synchronized).
(Not that I've attempted that yet, but I may in the future. I like that
it is possible.) Of course you don't have to take my word for it, it is
cheaper than ever to start trying out Fantasy Grounds, and of course I'm
a little biased because Fantasy Grounds has now made me enough side
income to buy a candy bar. I'd love to see them continue to succeed in a
ever more competitive VTT market.</p>
]]>
      </content:encoded>
      <category>game-design</category>
      <pubDate>Sun, 23 Nov 2025 00:00:00 GMT</pubDate>
    </item>
    <item>
      <title>Some of the Surprising Complexities of Voting on Books</title>
      <link>https://blog.worldmaker.net/2025/04/27/book-club/</link>
      <guid isPermaLink="false">https://blog.worldmaker.net/2025/04/27/book-club/</guid>
      <content:encoded>
        <![CDATA[<p>I offered to write a new voting tool for my favorite <a href="https://worldmaker.net/jocobookclub/">book club</a>. It
seemed a good use of winter doldrums and new tools. I had several ideas
I thought would be good for the club. I wanted another, easily open source
portfolio site for <a href="https://worldmaker.net/butterfloat/">Butterfloat</a> and to try to show off some of the
techniques I've been concerned with in my professional life that aren't
always obvious because most of that code lives (and dies) behind closed
doors. I had a couple other pieces of technology I wanted to play with,
either to learn them better or because they were cheap new options or
both.</p>
<p>I got further along in this nights and weekends hobby project than I
expected I would, especially as someone who has had to explicitly take a
&quot;no moonlighting&quot; stance for most of my career in a needed boundary to
avoid burnout. Some of that is the new tools at my disposal. Some of
that is because this project turned out to be fun in some surprising
ways.</p>
<p>I've taken technical liberties in how almost everything worked, but it's
also been interesting after the major efforts were done seeking &quot;Product
Owner approval&quot; from the current book club admin (who is not me). I
didn't want to take over club admin, I wanted to try to work to make
this something friendly for an existing admin to work with.</p>
<h2>With a Little Help from My (Artificial) Friends</h2>
<p>While I kept talking about wanting to build a cool new voting site for
my favorite book club, I was rubbing up against my own boundaries on &quot;No
Moonlighting&quot;. As users have been starting to use and settle into the
new site, I have been making a lot of jokes that this site is especially
brought to them thanks to my Junior Developers on this project: GitHub
Copilot and Good Scotch.</p>
<p>This is one of the best kinds of jokes because it is surprisingly true.
There's a style of LLM coding that has been called <a href="https://en.wikipedia.org/wiki/Vibe_coding">vibe coding</a>. One
January weekend I realized I had a fun vibe coding relative of my own
within this project. My Mastodon subject line (or the Welsh spelling
CWbject if you prefer) was even &quot;<a href="https://smeap.com/@max/113824187346382324">Weird Vibes (Software Engineering)</a>&quot;,
convergent evolution at work in describing the difference in a new
workflow. (I was not aware of &quot;vibe coding&quot; at the time, but now it's
such a common term.) My vibe was different from the one making the rounds
as the titular &quot;vibe coding&quot;. That January day I realized that my vibe
was sort of distinctly &quot;bougie&quot; in a fun way. I realized that I'd spent
most of that coding session feeling as if I was &quot;leaned back&quot;, in a nice
bath robe, a rocks glass of Scotch in hand, code reviewing the output of
GitHub Copilot, much more than &quot;leaned in&quot; and directly writing code as
I would normally be on a day job project.</p>
<p>I loved that vibe of leaned back, bougie code review enough that I was
immediately joking about starting a new consultancy focused entirely on
being &quot;What if Masterpiece Theater was about Code Reviews?&quot; I still kind
of think that would be a cool company. I don't know how much of a market
there would be for &quot;We rent nice mahogany offices, set up a good whiskey
bar, wear cool robes and smoking jackets, and only review code, we don't
write it&quot;, but if you are an investor looking for that opportunity, hit
me up, I've got ideas.</p>
<p>Moonlighting has less risk of burnout when it feels like distinctly
different vibes from day job work. My current day job does pay for GitHub
Copilot on my projects, but isn't currently encouraging me to work with
a good scotch or bourbon in hand. Also most of the things I'm working on
at my day job aren't necessarily easy off the shelf algorithms and &quot;basic
CRUD&quot; in the same way that this hobby project has been.</p>
<h2>The Schulze (or &quot;beatpath&quot;) Method and Rabbit Holes of Voting System Knowledge</h2>
<p>A lot of us get interesting hyperfixations at various times that stick
with us. One of my college ones was getting deep in trivia of Robert's
Rules of Order, where I generated half an idea that I wanted to build a
meeting presentation tool more tuned for parliamentary procedure than
PowerPoint. (That is maybe a fun idea to revisit with LLMs, though the
ultimate crash of that project was not wanting to get into the licensing
drama of modern Robert's Rules and not expecting the tool to be that
exciting for running contemporary meetings if sticking only to the
content of Public Domain versions). Related to that one, and both a
deeper rabbit hole and maybe more important to following years, was a
deep dive into the more arcane mathematics, game theory/economics, and
software algorithms related to many of the ways it is possible to
calculate the votes of a group to find the most interesting winner.</p>
<p>The most common voting systems in our lives are all, in one way or
another, &quot;first-past-the-post&quot; systems where a simple majority winner
takes all. The very well known failure cases of those systems that are
&quot;first-past-the-post&quot; are what leads to common modern problems like &quot;two
party systems&quot; and ugly compromises like slate voting and &quot;never vote
for a third party even if that's really what you want because that 'takes
away' votes from the next best candidate in one of the only two 'allowed'
parties&quot;.</p>
<p>There are lots of ways to solve this, but there's lots more ways that
feel like they solve this but are really just
&quot;first-past-the-post-with-more-steps&quot;. Ranked choice (rank some or all
the candidates from favorite to least favorite) often give the feeling
of solving this easily, with relatively &quot;easy&quot; ways to assign &quot;points&quot;
and build point systems that do easy math in a spreadsheet. No offense
to anyone that likes to run a voting system like that or how much
interesting work has gone into running such votes and tweaking point
systems, but the ease of doing that is deceptive that in how rare it can
be for your &quot;points&quot; system to help solve things like let the winner be
the candidate that would win the most head-to-head battles against all
of the other candidates.</p>
<p>The nerdy mathematical name for this goal that head-to-head battles
matter is the <a href="https://en.wikipedia.org/wiki/Condorcet_winner_criterion">Condorcet winner criterion</a> and you might be surprised
how many voting systems fail this criterion. It sounds like an easy
thing to do, but it's a lot harder in practice, mathematically, than it
sounds. (Also, I would be technically remiss if I didn't point out that
the Condorcet winner criterion is not the only possible criterion to
separate a &quot;good&quot; voting system from a &quot;first-pass-the-post&quot; one, there
are several &quot;competing&quot; criteria that have different trade-offs. I like
the Condorcet criterion best, because it allows for &quot;surprising&quot; winners
but winners that most voters can still agree should have won, which is
to say it is very good at avoiding &quot;two party systems&quot; where there's
only two choices and the rest is &quot;throwing your vote away&quot; and &quot;spoiling&quot;
a loser's chance.)</p>
<p>I became one of those nerds with a favorite voting system. That system
is generally referred to as the <a href="https://en.wikipedia.org/wiki/Schulze_method">Schulze method</a>, or the &quot;beatpath&quot;
method. (It does pass the Condorcet winner criterion.) The Schulze
method is a ranked pairs system (rank all candidates versus each other
in head-to-head battles) that mathematically accepts and flourishes with
ties so it can be <em>presented</em> as if it were a simpler ranked choice
system which allows ties. I think this is a surprisingly big deal: no
one really wants to vote for every pair of choices (think &quot;Round Robin
tournament&quot;), but present them a list of candidates to rank every one of
them 1-5 stars like they are writing Yelp Reviews and they can &quot;secretly&quot;
do all the work of doing a full pairwise ranking, with interesting ties,
and have fun doing it.</p>
<p>Unfortunately the Schulze method is easier demonstrated than described,
especially the math behind it. For a while there was a cool website called
<a href="https://modernballots.com/">Modern Ballots</a>, may it rest in peace, that I could point to do sample
votes. It was rather close to a &quot;Survey Monkey to make quick Schulze
ballots&quot;. With that website gone (but obviously not forgotten) it has
gotten a lot harder again to convince people to try Schulze method
voting. The math is just hard enough that no one wants to particularly
do it by hand (I don't), and it isn't easily illustrated how to do it in
an Excel sheet or Google Sheet, either. But the math is also so juicily
easy for a simple program to automate. The meat of the Schulze method
uses a slight variation on a simple textbook algorithm called the
<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
early in an undergraduate study of computing, because it also has deep
early computing roots from the time when &quot;Dynamic Programming&quot; meant
&quot;solving a problem in place in its own existing data structure&quot; rather
than something more exciting than that.</p>
<p>It's one of those algorithms that you may code one or two times for class
assignments and wonder if you'd ever actually need it in the real world.
It's one of those algorithms that code competitions love to include as
non-obvious solution to a word problem that then turns into a quickly
solved &quot;known algorithm&quot; project after you think long and hard about it.
The Floyd-Warshall algorithm is for finding the &quot;widest path&quot; in a
directed graph (digraph). Very simply: you've got paths from places like
A to B and A to C and C to B. These paths have numbers on them for some
reason that often is a variation of &quot;how wide is this path&quot; (how many
people can walk it side by side, how much money does it cost to pay the
tolls, how slow is it on a busy traffic day, all sorts of other
word problem variations like that). Is it a wider path to go directly
from A to B, or should you take the scenic route from A to C to B? These
sorts of questions are surprisingly common, so that undergraduate
implication of &quot;you should learn this because it is handy&quot; turns out to
be a real world thing sometimes.</p>
<p>The nickname for the Schulze method as the &quot;beatpath&quot; method comes
directly from this core reliance on the Floyd-Warshall algorithm: you
are looking for the widest path of candidates, no matter how convoluted,
who beats the most other candidates, in whatever surprising order the
graph returns. If you've encouraged a lot ties along the way, as in using
a simple, strict 1-5 &quot;stars&quot; choice, sometimes the widest paths are very
surprising, but in a fun way (and a Condorcet criterion way) that few can
argue with the results.</p>
<p>One of the reasons that this hobby project turned into &quot;bougie vibes
coding&quot; for me was entirely because the hardest mathematical piece of
the puzzle was very much writing a comment that I was about to use the
Floyd-Warshall algorithm adapted for the Schulze method and allowing
GitHub Copilot to spit out almost exactly the Wikipedia definition of
the Floyd-Warshall adjusted from Wikipedian pseudo-code to the language
I was actually working in with some of my coding style. I took the time
to review that it actually matched the definition and my code style, but
I was in a place to be very pleased that something I had written a bunch
of times before (as mentioned) was so easily plugged in for me here by
&quot;my Junior Developer&quot;, Copilot. Not all programming is &quot;use this very
well known algorithm in this rather well known use case&quot; (I'd argue most
isn't), but it was certainly exciting to see an example play out directly
here, and code review it in a bathrobe with a good Scotch in hand.</p>
<h2>Passkeys Are So Nearly (Sigh) the Present</h2>
<p>Years ago after too many near misses on database leaks and other concerns,
I made a decision that I never wanted to be in charge of storing passwords
in a database ever again. I hate passwords, personally. I especially hate
the feeling of the risk of someone's one-and-only password becoming
compromised due to my &quot;silly fun hobby project&quot;.  I've been advocating to
get rid of them in one way or another for many years. I was a fan of the
original Blogger's friend version of OpenID and was sad to see it devolve
into related-in-name-only standards like OpenID Connect. I was a
proponent for [Mozilla Persona] and still remain disappointed it didn't
succeed. (One of my few other moonlighting projects back in the day
that's fun to compare/contrast with this book club site was one that
<a href="https://github.com/WorldMaker/StingerCheck/">used Schulze voting and Mozilla Persona</a>. It died when Rotten Tomatoes
shut down their public API almost exactly a year into running that site,
but if not for that it would have died soon after that when Mozilla
Persona was shutdown.)</p>
<p>Passkeys are the current hope for the present of &quot;no passwords in my
database&quot;. It feels like they are ready for prime time and the mainstream,
we just need a bit more education, a bit more pomp and circumstance, a
tiny bit more polish in edge cases. It feels like we finally have a big
enough multi-vendor coalition it isn't destined to die like Mozilla
Persona did. Technically, it even feels like a slightly more polished
version of what Mozilla Persona wanted to be when it grew up (a way for
websites to get an ID directly from the user's browser with no middle
men), though it is missing some of the things that made Mozilla Persona
so nice to work with. A big one for me is that Mozilla Persona was
designed with the intent that there be an easy to verify claim of a
user's email address directly associated with the key/ID. As someone that
doesn't want to pay for a transactional email provider for my hobby
project, I would love for an email attestation on Passkeys to be something
easy to request and also to verify with a trustworthy third party. I also
understand why that's not currently in anyone's Passkey plans (and may
have played a part of why cross-vendor interest in Mozilla Persona was
so low).</p>
<p>This site was my first attempt to implement Passkeys as a so-called, in
security jargon, Relying Party (RP). I used an off-the-shelf library for
this called <a href="https://simplewebauthn.dev/">simplewebauthn</a> and hit some issues where despite the name,
it did not feel as simple as I would have liked. This is partly because
simplewebauthn, to stay simple, acts mostly as a lego kit with a gnarly
pack of step-by-step instructions, including many &quot;DIY Here&quot; steps, and
a wave for good luck. To my benefit, I haven't been doing anything
particularly exciting or different or weird with it, and I appreciated
it as a lego kit for not constraining my choice of front-end &quot;Frameworks&quot;
for my front end (as I'd already made my choice, which I will get to
later in this post), even as I kept hoping for an even simpler solution.</p>
<p>As much as I could complain about how many headaches it gave me in the
thick of choosing it and writing the code to glue it all together and
implement it, a lot of it really was just following the lego instructions,
and a lot of that did benefit from &quot;let Copilot write the first pass&quot; and
then clean up its assumptions and fix things specific to my backend and
frontend choices. Not exactly the same &quot;lean back&quot; experience of writing
the core voting algorithm but something similar feeling to that. This was
the first big project of the application (if you can't login, how can you
vote) before even building the voting code, and a lot of the
procrastination leading into the project was not wanting to build this
code in the first place. I'm glad I got through it, and I think I did a
strong job with the strange intricacies of logging in with Passkeys and
only Passkeys, as well as the bootstrap phase to register an account's
first Passkey. I can't say it is the most secure implementation (and
given a lack of transactional email provider to actually verify emails,
it certainly isn't), but it is also possibly overkill for a &quot;silly fun
hobby project&quot;. Yet I succeeded in not storing anything even resembling
a user password in the site's database.</p>
<p>Passkeys were one of my biggest anxieties in technical choices leading
into the &quot;MVP&quot; demo with the Product Owner. I was pleasantly surprised
at how well it demoed and the overall acceptance of it. Though I also
was lucky here that the Product Owner's background in information
security also easily agreed &quot;oh yeah, no passwords&quot;.</p>
<p>I also expected a lot more user support/training issues and/or complaints
with Passkeys and have been mostly pleasantly surprised with general user
acceptance. Again here some of that is probably the luck of the bias of
this book club in question to have a somewhat tech friendly background
overall.</p>
<p>The biggest issue seemed to be an old (already out of security support)
version of macOS claiming to support Passkeys but failing to register a
new one in any browser using the system keychain. The workaround seemed
to be to register on a recent enough iOS device and well enough I'm told
the Mac eventually synced that key and worked with it.</p>
<p>The next biggest issue has been Windows 10 which is in a similar place of
&quot;supports Passkey&quot; but has quirks with it and only syncs with Windows
devices. I had planned for this issue, as my own main development machine
on this project is stuck with Windows 10, and so I made sure that my
implementation supported registering multiple keys for the same email
address (which I've always taken for as table stakes in Passkey
implementation, but it's interesting how some still don't).</p>
<p>We also found out that the &quot;the Facebook (embedded) browser&quot; is generally
blocked from using Passkeys and/or doesn't implement Passkey support on
every phone OS. This has been particularly frustrating because sharing
links on Facebook has been common for the book club, which has used an
FB Group as a central communications channel since it started. It's easy
to forget that not everyone distrusts the embedded browsers in platforms
like Facebook or even understands the difference between opening a link
in an embedded browser and opening the link in the system browser/their
regular and default browser. Facebook doesn't help this by moving the
&quot;open in default browser&quot; option strangely hard to find. (Today it's
behind an ellipsis menu. Who knows where it will move tomorrow.)</p>
<p>I also tried to mitigate some feedback ahead of time by suggesting
logging in with an iOS or Android device first, because those have the
subtly strongest implementations, are slightly more likely to be somewhat
up to date (given mobile OS update policies and cell carrier enforcement
of some of them), and generally the best sync behavior to their
respective ecosystems. Windows 11 can do the QR code dance to login with
an iOS or Android Passkey then help you register a Windows Passkey. Some
Linux setups can do that now, too. I got some feedback on the earliest
wording of that suggestion that I was making it sound like the website
<em>only</em> worked on iOS or Android, and I was happy to reword that and also
still feel like I'm trying to find the best way to word that advice
(without also over-explaining it, as I do here).</p>
<p>A lesser pet peeve I have with Passkey UX is that I’ve implemented all
the markup for the best “autofill” experiences but it doesn’t light up
and I believe the reason for that is that in most browsers it still
assumes a password field. I’ve wondered if adding a dummy password field
might help browsers show the best UX, but that seems silly for a
Passkey-only website to do and I certainly don’t want to confuse my users
with a vestigial password field that doesn’t do anything just to autofill
their email address with a cute key icon next to it. I hope the browsers
improve the UX for “no password” sites, as much as I understand why the
UX is maybe overly focused on the chicken-and-egg bootstrap dance of
upgrading sites that still use “traditional” passwords first.</p>
<p>Of course, some of this user acceptance feedback still feels like &quot;early
adopter&quot; feedback and maybe I should still brace for more pain in future
registration waves as our least technical users find time to want to vote
or we find new club members with more diversity in their technical
backgrounds. But overall I think big takeaways are beware old Passkey
implementations that only sort of work, know your workarounds for that
(allow registering the same email twice; always support multiple Passkeys
in an account), and I wish there more examples of Passkey-only sites in
the open source zeitgeist to double check implementations on. Hopefully
this implementation will be another one of use to someone else next (even
if indirectly through better GitHub Copilot vibe coding, maybe).</p>
<h2>Web Components with Butterfloat</h2>
<p>I wanted another public, open source portfolio project for <a href="https://worldmaker.net/butterfloat/">Butterfloat</a>.
I'm still quite proud of Butterfloat and I know it isn't a &quot;Framework&quot; on
hardly anyone else's radar (and maybe can't be because it isn't churning
through backwards compatibility breaks fast enough 😼), but one of those
things that if I build cool things with it maybe I slowly convince more
people to try it. In particular, both of the other public sites that are
open source in my portfolio were migrated from Knockout and were single
pages (though one because it was built to be single-page-application-like
and the other only because it was a small demo with no need for a second
page). This site I knew I wanted an old school multi-page app, because I
wanted to use a static site generator to build as much as possible ahead
of time. I also knew going into this project that I wanted to start from
the perspective of a (&quot;traditional&quot; for a static site generator [SSG])
&quot;flat file database&quot; of Markdown files in folders (with some modest YAML).</p>
<p>I've had some ideas for building an SSG with Butterfloat, but this project
didn't feel right for experimenting with those ideas. Particularly with
the desire to use the Markdown files with frontmatter paradigm. I did
have fun discovering <a href="https://lume.land/">Lume</a> as a minimalist SSG with all the basics
covered that I expected and needed.</p>
<p>This seemed like the right project where I needed to finally test building
out Web Components with Butterfloat in a classic multi-page architecture.</p>
<p>Overall, I've been very excited from the results of building web
components with Butterfloat. I've mentioned many times that a guide star
for Butterfloat has been &quot;modern Knockout&quot; and it has been in building
these web components that I've felt some of the most like I've been
honoring the Knockout legacy. Knockout was critical in the early
&quot;Progressive Enhancement&quot; web, and web components, when they work well,
have a beautiful way of feeling like the endgame of Progressive
Enhancement. Simple DOM elements get replaced with more interesting things
if JS is available and as soon as it loads. That feels a lot like the best
of Knockout's experiences in the old days. Having the ability to do it
with much less of a &quot;flash of unstyled content&quot; is a strong improvement.
Web component elements themselves have no default content or styles and
if you place things like &quot;noscript&quot; warnings inside them it is a quick
matter to replace them on web component startup. Additionally, template
tags are better than what Knockout was doing with programming inside
comments and hiding things with display CSS at runtime. <a href="https://worldmaker.net/butterfloat/#/stamps">Stamps</a>
(&quot;server-side rendering&quot; of Butterfloat static DOM to template tags) have
been in Butterfloat for some time now, but Stamps definitely shine in the
context of web components, building template tags at “compile time” ready
for web components to pick up as soon as they are ready.</p>
<p>Going into building web components with Butterfloat I was worried that it
was going to be more complex and/or harder than it turned out to be. Given
some of the other web component libraries I've seen for other
“frameworks”, I expected to need a bunch of custom adapter code or things
of that nature, but I think the Butterfloat component model and lifecycle
sort of accidentally turned out to be perfect for running inside web
components and the amount of code to build a Web Component from a
Butterfloat Component seems almost too simple enough to me. I've
<a href="https://worldmaker.net/butterfloat/#/guides/web-components">documented the bones of the pattern</a> already and hope it helps other
projects looking for a lightweight alternative for a Web Component
&quot;framework&quot;.</p>
<p>Of course, to be fair, part of why this book club site has had such a
simple time with Web Components is that I intentionally eschewed the
Shadow DOM and there's nary a Shadow Root in sight anywhere in the
project. Turns out that is something that you <em>can just do</em>. I know a lot
of Web Components tutorials and discussions get very deep into the weeds
of the Shadow DOM, and I understand why so many libraries that build Web
Components may see needs for Shadow DOM tools, but also I think a lot of
the over-focus on the Shadow DOM does injustice to how simple they are
without it, and how much you can do with Web Components that don't have
Shadow DOM. But also, I'm a fan of letting CSS do what it does best at,
at a global level across the page, because that is a lot of power and the
Shadow DOM is partly about distrusting page styles rather than taking
advantage of them. I do like to choose to take advantage of them.</p>
<p>Building Butterfloat components with GitHub Copilot has been fascinating.
Obviously Copilot is trained on a ton of JSX from React projects. For the
most part a lot of that just works, though stylistically it’s nice to also
update it for shortcuts Butterfloat supports that React doesn’t (like
<code>class</code> over <code>className</code>). In a couple places Copilot has been useful in
helping me find React idioms that didn’t work in Butterfloat but could
(and was quickly upgraded to support) and ones that I still intentionally
did not wish to support preferring more Butterfloat-specific idioms. This
was also a fun case of watching Copilot pick up more and more of those
Butterfloat specifics from this project as it grew and presumably also
from rich code search of my other public and open source Butterfloat
projects.</p>
<p>My biggest pet peeve with building these web components is that the ESM
native, properly tree-shakeable version of <a href="https://rxjs.dev/">RxJS</a> is apparently currently
trapped behind waiting for various standards organization working groups,
because the current maintainers want to wait for “Signals” and possibly
browser-native Observables proposals to shake out first. I understand the
reasoning behind that (align to standards for the next SemVer major
release), but I don’t agree with it, because we’ve been on this
merry-go-round before with standards bodies almost doing native
Observables and then giving up after lots of hemming and hawing and giant
debates. The fact that this round also includes trying to standardize
Observables-but-dumber &quot;Signals&quot; drama doesn’t give me a lot of confidence
that things will turn out better this time than the last time, and in the
mean time as great as esbuild is, I still would love to get real
treeshaking from RxJS (Butterfloat’s one and only dependency).</p>
<h2>Deno KV and Over-Engineering a Vote Engine for a Larger Scale than Necessary</h2>
<p>For this project the backend database I chose was <a href="https://docs.deno.com/deploy/kv/manual/">Deno KV</a> on
<a href="https://docs.deno.com/deploy/manual/">Deno Deploy</a>. I evaluated a lot of &quot;serverless&quot; deployment tools and
their various database backends. There's a lot of great options today.
There are a lot of options with interesting marketing budgets and &quot;fan
bases&quot;. I started exploring Deno earlier when I made sure that Butterfloat
got a <a href="https://jsr.io/@worldmaker/butterfloat/score">good score on JSR</a> and found I liked a lot of the philosophy and
developer experience feel of it. (So much so I’m debating making
Butterfloat Deno and JSR-first and suggesting that over traditional npm
installs, but I’m not in a rush to do that.)</p>
<p>I'd pretty happily recommend Deno Deploy at this point. So far the
developer experience has been great.</p>
<p>Deno's Deploy product on paper seems to have fewer features that most of
the more popular/hyped options. It's primary database is &quot;just&quot; a &quot;simple&quot;
key-value store without a lot of bells or whistles like some of the hosted
SQL databases and/or &quot;NoSQL&quot; document databases that are &quot;standards&quot; in
this area.</p>
<p>But Deno Deploy's own focus on its documentation spoke to me. The
experiences with JSR and other parts of the Deno ecosystem have all been
pleasant and I think the Deno team seems to show a lot of maturity in how
they think about developer experiences. That focus on documentation means
their website leads relatively straight to the developer documentation,
rather than some of other hosting providers trying to steamroll you
through marketing hype after marketing hype page or blind trial sign-up
actions without first being able to read the developer document. (It also
helps that Deno Deploy seems to have a very generous free tier compared
to some of its peers.) A lot Deno's documentation alone makes up for how
relatively &quot;young&quot; Deno Deploy is and how many parts of it are still
appropriately and visibly labeled &quot;experimental&quot; or &quot;unstable&quot;, which
shows maturity, to me at least, in how everything is documented. It's
counter-intuitive but being able to clearly see the &quot;experimental&quot; and
&quot;unstable&quot; labels helps my impression a lot. To some extent a lot of
these &quot;serverless&quot; hosts still feel like <em>most</em> of their products should
be labeled &quot;experimental&quot; or &quot;unstable&quot; and Deno admitting to it feels
more mature to me.</p>
<p>As for Deno KV, I also knew from past experience that the border between
&quot;just&quot; a key-value store and &quot;a document database&quot; is really blurry,
especially if you don't mind doing a little bit of work up front on your
&quot;primary indexes&quot; (your key building patterns) and rolling your own
&quot;secondary indexes&quot; as needed. I especially loved that Deno KV brings its
own invisible &quot;path separator&quot; for its key namespaces. This makes it
easier to build smart &quot;primary indexes&quot; while also providing tools to
avoid some of the obvious problems like key injection attacks.</p>
<p>There is a lot you can do with &quot;just&quot; a KV, especially if you are willing
to over-engineer things a bit. I’ve certainly picked up familiarity over
the years from complex usages of things like redis caches and Local
Storage.</p>
<p>I'm quite proud of the voting engine in this book club site. It's designed
for a massive scale that this particular club doesn't really need, but it
was exciting to write it that way, and it is also leads to more than a
bit of &quot;penny pinching&quot; in interesting ways to help keep the site within
Deno's current generous free tier.</p>
<p>In this case the Schulze method is very amenable to a classic &quot;map/reduce&quot;
pattern, with each ballot being mapped to an adjacency matrix describing
the &quot;beats&quot; graph for that user, reducing those adjacency graphs through
a simple matrix sum aggregate, then taking the last summation matrix and
mapping that through the Floyd-Warshall algorithm to arrive at the final
matrix of the widest paths of all the votes.</p>
<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
work. Additionally, the users' ballots are partitioned into (currently)
32 random buckets (but flexibly more as needed) reducing the number of
adjacency matrixes that need to be resummed when a single user votes, as
a simple complexity reduction to what we call <code>O(log n)</code>. I'm proud of
this bucketing, which is accomplished through a bit of maybe silly but
handy &quot;primary index&quot; magic of using for the ballot keys the reversed
string of the user's ID for the ballot. User IDs are <a href="https://github.com/ulid/spec">ULIDs</a> which are
timestamp up front and random entropy at the back, so reversing the ID
gives random buckets (as opposed to time insertion buckets, which is
useful in other cases for things like clustered indexes and log-oriented
appends/merges). (String reversals of these basic 32-letter alphabet ASCII
strings are also easily reversable and/or repeatable, making it still easy
to do ballot lookups by User ID.) As far as hash bucketing schemes go,
it's not a very complex one, but it works well in this case.</p>
<h2>&quot;Offline First&quot; for the MPA World</h2>
<p>One of the things I’ve picked up from past SPAs that I’ve worked on is the
importance for designing for &quot;offline first&quot;. Some of the applications I
worked on for past jobs <em>demanded</em> &quot;offline first&quot; architectures, because
if you are in the field examining some remote reach of a river, you
aren’t likely to have good cell service no matter how close we feel to
having ubiquitous cell service coverage in the US.</p>
<p>But the thing I found from &quot;offline first&quot; is that it generally &quot;feels
better&quot; and that it often feels like how modern apps seem that they are
supposed to feel.</p>
<p>All of the CRUD (create, read, update, delete) work in the voting site is
designed to be &quot;offline first&quot;, using Local Storage generously and some
very simple &quot;three-way merge&quot; techniques. (CRDTs are fun, but not what I
thought was needed because ballots are intentionally single user.) This
&quot;offline first&quot; approach may seem especially like overkill for a
multi-page application because &quot;the next page&quot; isn’t likely to load when
offline, but I think people are surprised how well MPA applications get
cached and even when you are not intentionally expecting users to come to
the site while offline, the experience of becoming accidentally offline
or even just needing to &quot;save a draft until I come back&quot; is fantastic if
you've designed for &quot;offline first&quot;, I think. Because offline happens
unexpectedly all the time and people often want time to draft things and
come back. In a multi-page application it especially helps to give that
&quot;feel&quot; of a single page application, being able to make changes in one
page and reflect them in a second, without actually being a single page
application.</p>
<p>(Between Butterfloat components caching well, the speedy loading of
Stamp-based components, and the fast loading from local storage of
&quot;offline first&quot; data I’ve even heard surprised remarks that some users
thought it was a very well optimized single-page application. I’m sure
if I add CSS view transitions that illusion will feel complete. I may add
CSS view transitions. As others are also saying, it’s a good time to start
writing MPAs again. I do recommend considering &quot;offline first&quot; as a useful
tool even for an MPA.)</p>
<h2>Takeaways and Action Items</h2>
<p>I’m still skeptical about the long term viability of LLMs in software
engineering. It’s not going to replace most of what I do and I’m not the
sort to “vibe code” entire hobby projects (much less professional
projects) because I’m still me and saddled with a goal to over-engineer
for scale and reliability beyond the bare minimum, but hobby projects
start to be something I want to do again when I can lean back on a cold
winter’s day with a Scotch and code review some moron Junior Developer
that’s great at copypasta and Stack Overflow and Wikipedia cribbing
solutions. It makes the real engineering easier when the grunt work is
done so quickly. It’s nice to have &quot;a team&quot; for solo projects now. That
also helps hobby work feel more like &quot;senior-level&quot; work: explaining to
junior developers what to do is a good chunk of my day jobs and Copilot
takes instructions in similar (though not the same) ways.</p>
<p>I've got a couple wishlist items out of this project:</p>
<ul>
<li>I’d love for browsers to improve Passkey-only site UX</li>
<li>RxJS needs a good scoring “JSR build” in 2025 and the wait for it is
silly</li>
<li>Facebook really should add a way to link to sites explicitly in the
system's default browser, especially if OS-level Passkeys don't work
in the embedded browser</li>
</ul>
<p>I think there’s some useful Action Items for other projects coming out
of this one:</p>
<ul>
<li>Don’t underestimate the power of an MPA design in 2025</li>
<li>“Progressive Enhancement” is back and better than ever with Web Components</li>
<li>Try Butterfloat, it’s great and lightweight (single dependency! great DX!)</li>
<li>Passkeys are here now and mostly great</li>
<li>“Offline first” is great for everyone, even MPAs</li>
<li>The Schulze (“beatpath”) method is a great way to vote on silly things
like “next book to read” but also maybe serious things, and more people
should be aware of it</li>
</ul>
<p %="" post_url="" 2014-05-26-mozilla-persona-and-owin-part-2-supporting-bearer-="">[Mozilla Persona]:</p>
]]>
      </content:encoded>
      <category>coding</category>
      <pubDate>Sun, 27 Apr 2025 00:00:00 GMT</pubDate>
    </item>
    <item>
      <title>Butterfloat 1.5.0: Stamps for Static Rendering</title>
      <link>https://blog.worldmaker.net/2024/11/17/butterfloat-stamps/</link>
      <guid isPermaLink="false">https://blog.worldmaker.net/2024/11/17/butterfloat-stamps/</guid>
      <content:encoded>
        <![CDATA[<p><a href="https://worldmaker.net/butterfloat/">Butterfloat</a> released a big 1.5.0 with a big new building block
(nicknamed <a href="https://worldmaker.net/butterfloat/#/stamps">Stamps</a>) for server-side rendering and static ahead-of-time
rendering for the static HTML parts of components. This meets a bunch of
under-spoken needs for JS-based view engines, fulfills a big early
promise of Butterfloat, and does it with hopefully style and convenience.</p>
<h2>The Promise of Progressive Enhancement</h2>
<p>My guiding star for Butterfloat from the beginning was &quot;Modern Knockout&quot;.
There's influences from React, and lessons learned from Angular's mess,
but I felt like there was also some things missing that Knockout was
great at which have been missing for a while.</p>
<p>One of the biggest of those, for me, was how Knockout was built during
and somewhat epitomized (for me, at least) the somewhat now broken
promises of the now passed &quot;Progressive Enhancement&quot; era.</p>
<p>I had made a promise to myself to include some &quot;Progressive Enhancement&quot;
tools in Butterfloat and enshrined it in the issue tracker as the first
and most important issue. It was something I prepared for in the base
&quot;DNA&quot; of the design. It just didn't make the &quot;MVP&quot; cut of the original
release.</p>
<p>I'm excited that Butterfloat 1.5.0 fulfills that promise for the first
time with Stamps. (It's an early &quot;anniversary&quot; gift, almost exactly a
year after the first release.)</p>
<h2>The Need for Progressive Enhancement Never Left</h2>
<p>One of the prods that pushed me to think now was a great time to deliver
on a promise of &quot;Progressive Enhancement&quot; were multiple calls to do it
in recent news and reports. Several great articles have been written in
recent months on the accessibility problems created by the &quot;modern
frameworks&quot; of today's React and Angular for users on cheap devices and
bad connections. Because these sorts of frameworks have become the
&quot;default&quot; or the &quot;best practice&quot; or the &quot;easiest road&quot; in so many places,
they've become the tools in use even in places that <em>should</em> be
prioritizing accessibility.</p>
<p>I don't think Butterfloat will single-handedly fix the anti-trend, but
having tools for &quot;progressive enhancement&quot; was still important to me and
the time is always &quot;now&quot; and I appreciated the reminder that there were
good reasons to do it to help people that need it. Even if Butterfloat
might not be the most common choice to help solve that, I hope it helps
at least one other developer to have another option to point to that can
do it and that looks good.</p>
<h2>Keeping it Easy</h2>
<p>The Butterfloat component model was already focused on making static
HTML obviously distinct from change bindings (via Observables). Due to
this you can build a Stamp from any easily testable Component (and most
Components can be easily testable). There's no difference between a
Component that supports Stamps and one that doesn't. Stamps are always
bindable at runtime, there's no need to choose between interactivity and
static building a Stamp, there's no architecture/strategy/pattern like
&quot;Islands&quot; to need to rewrite to. It's truly &quot;progressive enhancement&quot;.</p>
<p>Stamps continue the theme of taking more modern approaches than the
state of the art from the Knockout days. Where Knockout used a DSL
written in comments and sometimes showed a flash of incomplete content
while the library was loading. Stamps by default build into modern
<code>&lt;template&gt;</code> and <code>&lt;slot&gt;</code> tags. They won't be rendered at all in modern
browsers until instantiated or unless &quot;prestamped&quot; into the container.</p>
<h2>More is Possible</h2>
<p>I keep referring to Stamps as just the &quot;low level&quot; building block for
progressive enhancement/SSR/SSG. There are still more ideas to explore
from them as a starting point:</p>
<ul>
<li>Because Stamps build to <code>&lt;template&gt;</code> and <code>&lt;slot&gt;</code>, there may be good
scenarios to enable rendering them via the &quot;Shadow DOM&quot; rather than
binding them with Butterfloat.</li>
<li>There may be more room for writing Butterfloat components &quot;Stamp-first&quot;.
Stamp bindings are not intended to be edited by hand, but they follow
patterns that <em>may</em> be amenable to future tooling.</li>
<li>Stamps currently exist entirely within Component boundaries. It would
be nice to have tools to merge stamps and encompass larger trees of
components in a single Stamp. That would open up more static site
generation (SSG) opportunities.</li>
<li>I've long been meaning to explore Observable completion as a tool for
&quot;binding removal&quot; in SSG.</li>
<li>Internally Butterfloat wires many things using &quot;anonymous&quot; Components
at runtime. It is not currently that useful to run an entire Butterfloat
app only with Stamps and treeshake out the static DOM builder. (Though
this does not save that many KBs.) It might be nice to explore solutions
to that. Especially for ideas of trying to run Web Components as just
Butterfloat Stamps and try to treeshake golf the components.</li>
</ul>
<p>I don't know when I'll prioritize any of that work, given at the moment
this is mostly a &quot;free time&quot; project (though a Production-ready one, if
I may say so). I'd love contributors if any of the above ideas or others
spark interest.</p>
]]>
      </content:encoded>
      <category>coding</category>
      <pubDate>Sun, 17 Nov 2024 00:00:00 GMT</pubDate>
    </item>
    <item>
      <title>Modern Node Packaging and Bundler-Free Living</title>
      <link>https://blog.worldmaker.net/2024/03/13/node-packaging/</link>
      <guid isPermaLink="false">https://blog.worldmaker.net/2024/03/13/node-packaging/</guid>
      <content:encoded>
        <![CDATA[<p>I've found myself giving multiple light tutorials on how simple modern
ES Module-only packaging can be in Node/npm packages. I've similarly
given several tutorials on deploying web apps &quot;bundler-free&quot; (it's
more possible than many think). There's a lot of outdated advice on
these subjects, and some interesting superstitions, so I thought it
would be useful to blog about the state of things in 2024 as I know
them and hopefully update some out of date advice with helpful advice
that you can be lazier and things may be simpler than you think and a
lot of the confusion is because of outdated advice.</p>
<p>I'll get into details at length, and some digressions along the way,
but I think the top-level summary is simple and worth bullet pointing
up front. If you are looking to build, bundle, and/or package projects
for any or all of Node, npm, bundlers, and/or the browser, things are
much simpler if you only build/bundle/package ES Modules:</p>
<ul>
<li>You only need <code>&quot;type&quot;: &quot;module&quot;</code> and <code>&quot;exports&quot;</code> in <code>package.json</code></li>
<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
<code>&quot;main&quot;</code> was</li>
<li>Every tool that understands <code>&quot;exports&quot;</code> understands <code>&quot;type&quot;: &quot;module&quot;</code></li>
<li>Use only the <code>.js</code> file extension</li>
<li>Use <code>.js</code> file extensions for relative imports</li>
<li>Aside: Consider mocha or <code>node --test</code> for unit testing</li>
<li>You might not need a bundler (or you may just need less bundling
than you think)</li>
<li>Aside: You don't need Babel anymore</li>
</ul>
<h2><code>&quot;type&quot;: &quot;module&quot;</code></h2>
<p>The simplest thing to do when going ES Module only is to add a
<code>&quot;type&quot;: &quot;module&quot;</code> to your <code>package.json</code>. That does most of the work.
It says all <code>.js</code> files are ES Modules. You mostly only need the <code>.js</code>
file extension for everything at that point, so long as everything is
modules and you <em>want</em> everything to be modules. You can forget entirely
you ever heard the file extension <code>.mjs</code> and you can mostly avoid ever
needing a <code>.cjs</code> file and that only for increasingly rare developer
dependencies.</p>
<p><code>&quot;type&quot;: &quot;module&quot;</code> works in all currently security supported Node
versions. It works for a few versions back out of security support,
too, now.</p>
<p>In your package itself: You don't need to build any CommonJS files. You
don't need to build any AMD or UMD files. Let everything be ES Modules.
In your development processes CommonJS should be rare to non-existent
and can use the <code>.cjs</code> file extension.</p>
<p>Yes, you need to have the <code>.js</code> file extension on all your relative
file imports. The browser expects filenames to be specific and include
the file extension. Deno and Bun both require it. Most recent versions
of Node in <code>&quot;type&quot;: &quot;module&quot;</code> packages also require it, though there's
still a few ways that slips through the cracks. It's only some bundlers
that don't require it today.</p>
<h3>Typescript</h3>
<p>I love Typescript. Typescript is great. Just set Typescript to target
ES Modules, and you only need to target ES Modules. Just drop the JS
files in place (the default build configuration) and include the <code>.js</code>
file extension in all your relative imports. (Yes, <code>.js</code> not <code>.ts</code>.
Typescript decided it was better not to transform this and to emit it
as-is, so it wants you to use <code>.js</code> just like Node or the Browser
expects at runtime.) Typescript tries to auto-detect if you are using
file extensions and will start adding the <code>.js</code> for you in its
refactoring/auto-complete added auto-imports once you do it enough.</p>
<p>My advice lately is that a reasonably good combo (per caniuse
statistics) for <code>&quot;module&quot;</code> and <code>&quot;target&quot;</code> in your <code>tsconfig.json</code> is
<code>es${new Date().Year - 2}</code>. I've been using <code>&quot;es2022&quot;</code> lately for both.</p>
<h3>Exception: eslint</h3>
<p>The only CommonJS file I find I need today is the .eslintrc.cjs file.
It's the only place in my projects recently that you will see a CommonJS
file at all, and certainly the only need I have for the <code>.cjs</code>
extension. Because it is the only exception I currently have, it feels
worth naming and shaming the one tool that doesn't yet support ESM
that I like to use. That looks to finally be fixed in upcoming eslint 9.</p>
<h3>Aside: Testing</h3>
<p>Jest still calls ES Module loading &quot;experimental&quot; and has weird bugs
about it. Karma is terrible. (Node is already a v8 instance, you don't
need to boot another v8 instance from your v8 instance just to run
unit tests. That's not unit testing, that's some weird relative of
integration testing.) I recommend mocha or <code>node --test</code>. Mocha is
ancient (older than jest), and minimalist, but was also one of the
first harnesses to hand ES Modules well. (Sometimes the old dogs learn
new tricks better, I suppose.) <code>node --test</code> is the new kid on the
block, new enough many developers haven't yet discovered it. It's
great to have a test harness built-in to Node. That's one fewer
dependency you don't need. It's similarly minimalist to mocha, but
slowly gaining features, too. You can import all the asserts you need
from <code>'node:assert/strict'</code> and the <code>describe</code>/<code>it</code> test suite setup
functions from <code>'node:test'</code>.</p>
<h2><code>&quot;exports&quot;: &quot;./main.js&quot;</code></h2>
<p><code>&quot;type&quot;: &quot;module&quot;</code> does most of the work at runtime, but if you are
packaging you need <code>&quot;exports&quot;</code> in your <code>package.json</code>. Don't include
<code>&quot;main&quot;</code>. (There are no bundlers that understand <code>&quot;exports&quot;</code> but not
<code>&quot;type&quot;: &quot;module&quot;</code>. There are bundlers that see <code>&quot;main&quot;</code> and
accidentally or intentionally ignore <code>&quot;type&quot;: &quot;module&quot;</code>.)</p>
<p><code>&quot;exports&quot;</code> has a bad reputation for being complicated and confusing. It
can be as simple as <code>&quot;main&quot;</code> if you let it: <code>&quot;exports&quot;: &quot;./index.js&quot;</code>.</p>
<p>The requirements to use simple <code>&quot;exports&quot;</code> are basically everything this
guide is about: ES Modules only, with <code>&quot;type&quot;: &quot;module&quot;</code>, and one &quot;front
door&quot; for &quot;bare&quot; Node package imports.</p>
<p>The useful difference between <code>&quot;main&quot;</code> and <code>&quot;exports&quot;</code> is that
&quot;front-door&quot; only approach. With <code>&quot;main&quot;</code> Node would still let you
import from any .js file in the package and with <code>&quot;exports&quot;</code> you only
get the declared exports and nothing else, no more &quot;sneaking in the
back-door&quot;. This is useful as a package author for properly
establishing your public API. This is useful as a library consumer for
making importmaps easier to build (less accidentally importing
sub-files after the well known &quot;bare&quot; imports).</p>
<p>Even if you do need more than one &quot;front-door&quot;, you can still use a
simpler form of <code>&quot;exports&quot;</code> and ignore all the suggestions regarding
<code>&quot;import&quot;</code>, <code>&quot;require&quot;</code>, and <code>&quot;typings&quot;</code> subkeys:</p>
<pre class="shiki laserwave" style="background-color:#27212e;color:#ffffff" tabindex="0"><code class="language-json"><span class="line"><span style="color:#7B6995">{</span></span>
<span class="line"><span style="color:#7B6995">    "</span><span style="color:#40B4C4">exports</span><span style="color:#7B6995">"</span><span style="color:#7B6995">:</span><span style="color:#7B6995"> {</span></span>
<span class="line"><span style="color:#7B6995">        "</span><span style="color:#40B4C4">.</span><span style="color:#7B6995">"</span><span style="color:#7B6995">:</span><span style="color:#7B6995"> "</span><span style="color:#B4DCE7">./index.js</span><span style="color:#7B6995">"</span><span style="color:#7B6995">,</span></span>
<span class="line"><span style="color:#7B6995">        "</span><span style="color:#40B4C4">./optional-cool-thing</span><span style="color:#7B6995">"</span><span style="color:#7B6995">:</span><span style="color:#7B6995"> "</span><span style="color:#B4DCE7">./cool.js</span><span style="color:#7B6995">"</span></span>
<span class="line"><span style="color:#7B6995">    }</span></span>
<span class="line"><span style="color:#7B6995">}</span></span></code></pre>
<p>Typescript since 4.7 has no problems picking up typings from packages
that include the TS sources or obviously named <code>.d.ts</code> files for any
<code>&quot;exports&quot;</code> configuration, including these simple ones, so long as files
are simply predictably side-by-side.</p>
<h2>Bundler-Free Living</h2>
<p>You might not need a bundler anymore, at least in development, but
sometimes even in Production you need less bundling than you think in
2024.</p>
<p>You can use an importmap to import &quot;bare&quot; Node package names in the
browser.</p>
<p>ES Modules work great in today's browsers with <code>&lt;script type=&quot;module&quot;&gt;</code>.</p>
<p>In a growing number of the simplest cases you can just prune and ship
<code>node_modules</code> to a web server, with an appropriate importmap.
<code>npm prune --omit=dev</code> will remove all of your development
dependencies. You may need to spot check for large files like test data
to also prune.</p>
<p>You may need to bundle some of your dependencies, especially ones that
don't yet ship ES Modules or haven't yet adjusted to including .js file
extensions for browser compatibility. You can bundle one dependency at
a time. &quot;Vendorizing&quot; your dependencies like this can be a one-line
command with a bundler that itself outputs ES Modules. I use esbuild
for this with the <code>--format esm</code> flag, it's generally a simple one-line
CLI command that I can include in <code>package.json</code> <code>&quot;scripts&quot;</code> and/or
documentation.</p>
<p>ES Modules loaded as ES Modules is a great development experience in
the browsers. You may not even miss &quot;hot reloading&quot; (and you can try
to implement it without the big frameworks and bundlers if you still
want it).</p>
<p>In HTTP/2+ and some well configured/behaved HTTP/1.1 servers there's a
lot less of a &quot;per-file&quot; performance hit than developers historically
worried about. Many ES Modules libraries are collections of lots of
small files, but the always asynchronous loading of
<code>&lt;script type=&quot;module&quot;&gt;</code> combines with some optimizations in dependency
graph loading for smoother performance than many old pre-bundler
memories.</p>
<p>You can use Browser performance tools to make bundling decisions based
on real production data. Rather than always bundle everything, see
what your actual browsers and servers are doing with real world
dependency graphs. You may find they download less overall than your
previous bundles. You may find that you only need to bundle specific
sub-graphs of your dependencies as they become specific problems.</p>
<p>You don't have to take my word on it, or any framework's big default
bundling configuration: try it yourself and use real data. You may be
surprised at how many fewer reasons to bundle there are with ESM. (I
haven't been that surprised: I shipped AMD applications a long time
ago with very little bundling, and that was just HTTP/1.1. ESM flies
higher and further, especially on HTTP/2+.)</p>
<h3>Useful Tweak: <code>&quot;sideEffects&quot;: false</code></h3>
<p>If you package a library and expect someone (including yourself) to
use certain bundlers, especially Webpack, it may make sense to still
include the non-standard <code>&quot;sideEffects&quot;: false</code> flag in your
<code>package.json</code>. This suggests to Webpack (and the few others) that it
may tree-shake your library at its most aggressive. Many other bundlers
do either deeper closure parsing or trust <code>&quot;type&quot;: &quot;module&quot;</code> says you
know what you are doing with ES Modules and don't have CommonJS
transition code and synchronous requires.</p>
<h3>Aside: So long, Babel (Thanks for All the Fish)</h3>
<p>You don't need Babel in 2024. It was a great tool for its time. The
number of polyfills and actual amount of transpiling you need for
compatibility in 2024 is minuscule (check caniuse statistics if you
don't trust me on this). If you need JSX consider using something
lighter like Typescript and/or esbuild. Everything else you think you
might need is already in Node or your Browser now.</p>
<h2>Living Example</h2>
<p>If you want to see an example library packaged this way, with an
encouragement to try bundler-free living (at least in development),
and with direct documentation on these same subjects, I can offer
<a href="https://github.com/WorldMaker/butterfloat/">Butterfloat</a> as one such
useful open source library.</p>
<p>The <a href="https://worldmaker.net/butterfloat/#/getting-started">Butterfloat Getting Started Guide</a> includes some
similar recaps to this blog post, including an example of bundler-light
living, showing how to spot bundle just the RxJS dependency and then
use an importmap for the vendorized RxJS and the <code>node_modules</code> &quot;bare&quot;
package import of Butterfloat to setup a very light-weight development
environment.</p>
<h2>Summary</h2>
<p>In the ES Modules-only world of Node/npm/browsers/bundlers, everything
should start to be easy again. Use <code>&quot;type&quot;: &quot;module&quot;</code> and you can
ignore a lot of old advice and/or outdated confusion.</p>
]]>
      </content:encoded>
      <category>coding</category>
      <category>javascript</category>
      <category>typescript</category>
      <pubDate>Wed, 13 Mar 2024 00:00:00 GMT</pubDate>
    </item>
    <item>
      <title>What is Butterfloat and Why is it The Greatest?</title>
      <link>https://blog.worldmaker.net/2023/11/28/butterfloat/</link>
      <guid isPermaLink="false">https://blog.worldmaker.net/2023/11/28/butterfloat/</guid>
      <content:encoded>
        <![CDATA[<p>I've been working on a modern web view engine named
<a href="https://github.com/WorldMaker/butterfloat">Butterfloat</a> (now in pre-release) that is
interestingly Knockout-inspired, but with a focus on Typescript and
as pure as possible RxJS Observables. I think it has been a great
project, and I'm proud of the results I've gotten as a solo
developer doing it in the middle of other tasks in just a few months.</p>
<h2>How Did We Get Here?</h2>
<p>I've been on an interesting journey for some time now. It started
with me criticizing some deep architecture decisions of Angular and
how their wishy-washy approach to RxJS made mistakes too common and
too easy. From the things that I experienced getting strong
performance out of an RxJS-heavy Angular application was an effort
of futility and required fighting uphill against a lot of &quot;Angular
Best Practices&quot; as seen in an ecosystem full of blog posts and Stack
Overflow answers available for copy and paste.</p>
<p>What happened next was that I hit a Production issue in Angular that
was precisely the sort of ghost story that developers least want to
experience: it was a massive, reliably repeatable performance stall
out that debug builds could not reproduce at all, even when pointed
to Production data and following every one of the same steps,
meticulously. This led me to deep diving into ways to remove Zone.js
and many aspects of Angular's change detection that most application
developers using Angular don't seem to encounter. That led me to the
need to pursue a &quot;Component Framework&quot; to help build RxJS-focused
components in a way that tried to lead to a &quot;pit of success&quot; in
terms of RxJS best practices, removing the need for Zone.js, and
taking a different approach to Angular change detection. I called
that framework Pharkas after fictional videogame pharmacologist,
Freddy Pharkas. (Because I felt like I too was a frontier pharmacist
slinging Rx in a lawless frontier.)</p>
<p>I had a few people ask if I would consider taking the lessons learned
from Pharkas and some of the ideas posited on how to build an
RxJS-focused templating language and build something from scratch
from that. I considered it more than once, but wasn't sure that
would be an itch I felt a need to scratch. Surely someone else might
build that at some point, and generally I've been happy with React
as &quot;good enough&quot; for most projects.</p>
<p>In August of this year, after more than 8 years in that position, I
was unceremoniously tagged in a &quot;headcount reduction&quot; due to a new
incoming CTO and an expectation of a shift in software strategies.</p>
<p>Among the many, many tasks involved in a full time job search, I
started working on one of the more unique to a software developer:
polishing my GitHub profile and revisiting some of my public
repositories on GitHub. Several of mine were written in Knockout,
which was the style at the time, or which was a preference of mine
for &quot;quick and dirty proof of concept&quot;. In dusting the cobwebs,
dealing with the bitrot, paying down some of the tech debt of the
<a href="https://github.com/WorldMaker/compradprog">CompRadProg</a> demo, in particular, I was thinking that
it might be great to upgrade it to something modern. The more I was
looking at it though, the more I was torn by how &quot;elegant&quot; some of
my View Models have been in Knockout, including in this &quot;quick and
dirty proof of concept&quot; demo that I love to talk about but don't
really have much left to do with it.</p>
<p>In thinking on this, it started to seem that the tools were in my
power, that I should just build my own view engine with real, pure
observables and Typescript and otherwise mostly vanilla ES2022+. I've
tried to stick to one major runtime dependency: RxJS. I've tried to
stick to one major build-time dependency: Typescript. (You could
use Babel instead, but you probably don't need Babel. I use esbuild,
myself. But as just a view engine, Butterfloat in unopinionated on
your build tooling choices.)</p>
<h2>How Is It Different From Most View Engines?</h2>
<p>I think a lot of the modern web frameworks have learned from Knockout
in some way or another. The impression I have of Angular, Vue,
Svelte, and Qwik is that all of them were too enamored with the
&quot;magic&quot; of Knockout's &quot;computed&quot; observables, and never quite
learned some of the lessons that Knockout's observables are more
accurately Subjects in modern observable nomenclature and the leak
of imperative concerns across API boundaries was never quite seen as
the problem it should have been. To me, that was often the weakness
of large Knockout codebases and seems the continued weakness of many
of today's Knockout successors, too.</p>
<h2>How Is It Different From React?</h2>
<p>This is where things start to get interesting. A simple Hello World
example at first glance looks a lot like an ordinary React function
component. I wanted the only compiler involved to be Typescript as
much as possible, so Butterfloat makes heavy use of TSX
infrastructure which is already an HTML-like template engine with a
lot of features it would take me quite a bit of effort to reinvent
from true scratch. A Butterfloat Component is a function. (It's
always a function at this time, there's no equivalent to a React
class component at all.) Just as with React, it can take as a first
argument some number of properties that reflect the &quot;attributes&quot; in
TSX that were passed to the component.</p>
<p>That's where things start to diverge. A Butterfloat Component takes
an optional second argument called the Component Context. This
context provides some useful helpers, which we'll get to.</p>
<p>On top of that, a Butterfloat Component is static by default.
Butterfloat is not a Virtual DOM. In a Knockout-inspired feeling that
more of an application's DOM is static than not, it has no diff and
patch mechanisms of the intermediate description language its TSX
compiles to. The only parts that can and will change once
instantiated to the DOM are things bound to Observables and other
Components (which of course &quot;secretly&quot; themselves become
Observables, too, at run time).</p>
<p>There are testing benefits to having this rich &quot;intermediate
description language&quot; similar to the virtual nodes of most Virtual
DOM engines. Some types of Butterfloat Components may be tested
entirely in Node without a need for useful DOM faking/testing
library such as JSDOM. The descriptions can even be richer than is
typical in a Virtual DOM environment because Butterfloat doesn't
expect most of them to be long-lived or commonly created so it
doesn't need to try to save space by using shorter names or other
such clever shortcuts. On top of that the descriptions are natively
written in Typescript so benefit from some type-level distinctions
that you don't commonly see in Virtual DOM virtual nodes.</p>
<p><a href="https://cycle.js.org/">Cycle.js</a> is a great Virtual DOM with Observables, if that
is what someone is looking for. Butterfloat tries to be a &quot;static
DOM&quot; with Observables in a way that I feel like I haven't really
seen since Knockout.</p>
<p>One further divergence that I'm particularly proud of is that
<code>@types/react</code> is a multi-thousand line file of seemingly
hand-maintained types, other JSX/TSX implementations either copy
and paste this work and hand merge it, or don't bother entirely. I
built a much fewer line bit of meta-typing on top of Typescript's
(auto-generated) <code>lib.dom</code> types. I think I've got a <em>better</em>
developer experience than React at a fraction of the cost of
labor (no matter how much of that labor is volunteer work by
DefinitelyTyped organization contributors). (One of the ways it is
better: the auto-generated <code>lib.dom</code> types include MDN direct links
in the documentation comments. I get those &quot;for free&quot; by
inheritance.) I expect other JSX/TSX implementations to learn from
this example now that one of us has done it, and I keep debating if
I want to try the political game of PRing something like it to
good old <code>@types/react</code> itself.</p>
<h2>Where Did The Name Come From?</h2>
<p>My design documents for this project for a few days were in a folder
called Dr. Mario after the only other, better known, fictional
pharmacist in videogames, in direct relationship to my Pharkas
project. I spend those days searching for a better name. I was at a
football game where I was especially thinking about how much I'd like
to somehow honor Knockout in the name without sounding too directly
related to Knockout (or its once and future intended successor Total
Knockout). By that point I realized that the thing most center in
the distance that I was staring out at, mostly unfocused, while
thinking about this project was the logo for the Louisville Muhammad
Ali International Airport. One of Muhammad Ali's well known
catchphrases was the classic &quot;Float like a Butterfly, Sting Like a
Bee&quot;. That seemed like a good idea for what I was going for with any
sort of Knockout-inspired project and satisfies the boxing metaphor,
albeit obliquely.</p>
<p>In the spirit of the Greatest Champion of All Time, I feel in a good
place to proclaim that with Pharkas I built a tool better than
(baseline) Angular and here with Butterfloat I have built a tool
greater than React in the spirit of Classic React (just a web view
engine). It is the greatest view engine for the modern web.</p>
]]>
      </content:encoded>
      <category>coding</category>
      <category>rxjs</category>
      <pubDate>Tue, 28 Nov 2023 00:00:00 GMT</pubDate>
    </item>
    <item>
      <title>The Prismatic Decimal System</title>
      <link>https://blog.worldmaker.net/2023/11/16/prismatic-decimal-system/</link>
      <guid isPermaLink="false">https://blog.worldmaker.net/2023/11/16/prismatic-decimal-system/</guid>
      <content:encoded>
        <![CDATA[<p>I'd had a passive interest in finding this novel for years. It had
been first mentioned to me in the footnote of an autobiography of a
modest local 1950s television star as a favorite novel of her
childhood. Though she referenced it only directly in passing in the
flow of a larger discussion of her much beloved children's show, there
was a sense that perhaps this book played a larger role in some of the
creative spark of the show as a whole. I had a sense that there were a
lot indirect references and in jokes to that novel in that section and
I was curious to find out more. It seemed implied that the book was a
well beloved book at least regionally and the author presumed some
familiarity with the contents of that book. I didn't think I could
tell for sure without reading that novel on my own so I added it to a
wishlist at the local free public library.</p>
<p>The library's online records told me the history of only the last
three copies of the book in the system, though I was glad for whatever
librarian had inputted even that much information into the digital
systems about a 1910s small press book from a publisher that lasted
only a dozen years and never really sold much outside this city.
Unfortunately for my search the last three copies were listed as
&quot;damaged beyond repair&quot;, &quot;sold at fundraising auction&quot;, and &quot;on
inter-library loan; overdue for return as of 03/1972&quot;. I figured that
was the end of my search, but I knew enough to leave it on my wishlist
as sometimes inter-library loans would turn back up (even ones somehow
decades overdue) or a librarian would spot an archived copy. What I
thought most likely this century and given the book was on the edge of
being public domain even in 1972 (given the publisher bankruptcy, and
no known author's estate, probably extremely likely it was already
public domain then) sometimes a scanning group would find a copy and
build an ebook. The wishlist had automatically sent me e-copies of
other strange finds over the years.</p>
<p>What I least expected was the handwritten letter that arrived on the
library's letterhead years after I'd even forgot placing it on my
wishlist. It was a pleasant surprise and I was almost surprised how
much I appreciated it more then than had it shown up as email.</p>
<p>&quot;Dear Ms. Lynne,</p>
<p>&quot;It came to my recent attention in discussing books with one Mr.
Forsberg that he has a copy of J. Y. Milburn's <em>Flight Into Night— A
Slumbering Fantasia</em> in his private collection. I mentioned knowing at
least one library patron with it wishlisted as to read for several
years ongoing and that apparently piqued Mr. Forsberg's curiosity. Mr.
Forsberg made it clear that he does not loan books of such vintage to
the library or its patrons as a general policy, but that this was a
particularly interesting book worth loaning to a new reader, if he
were given the opportunity to meet with the patron, face to face,
first. It's rarely good library policy to recommend engaging a private
collector in a direct manner such as this, but Mr. Forsberg is a close
friend both to me personally and to the library system as a whole and
I promised to at least pass the offer to visit his private collection
and maybe loan this book you've been looking for. Mr. Forsberg assured
me that it would be worth your time to read this book and he'd love to
make that connection. Please find attached Mr. Forsberg's card should
you be interested.</p>
<p>&quot;Regards and thank you for patronage and the time out of your day to
read this letter,</p>
<p>&quot;Verity Hayward&quot;</p>
<p>I had no trouble finding the card Mx. Hayward had attached to the
letter. &quot;X. Forsberg, Raconteur (Retired)&quot; was the dominant text on
it, with only a phone number, what appeared to be a bare street name
with no other address part but the county's name beside it. I was
weary about anyone that might call themselves a &quot;raconteur&quot;, of
course, but something about that &quot;(Retired)&quot; seemed humanizing to me,
and I wasn't sure why I felt that way.</p>
<p>It took me a few days to decide if I would reach out to Mr. Forsberg.
It seemed like a lot of effort to go through just for a chance to read
a book that had been suggested to me merely by recommendation of a
footnote of an autobiography. But I was also never one to turn down
any kind recommendation from a librarian, and especially not one sent
as a handwritten note (though admittedly this was my first). I have
always trusted librarians as a general rule, but if you cannot trust a
librarian named after &quot;Truth&quot; I'm not sure who in the world you could
trust, even if I don't think I've met Mx. Hayward in my tiny branch I
most often frequent.</p>
<p>Pushing my courage and pressed by my curiosity on one rainy afternoon
I finally convinced myself to call the number on Mr. Forsberg's card.
It took me a moment to find where I had last hidden the Phone app on
my home screen, having so much less need of it this century. This
ritual search added its own little needles of anxiety into the
process. I was surprised when the ring was answered almost immediately
by what sounded like a aged and yet sweetly voice, &quot;Mr. Forsberg's
private residence, who may I say is calling?&quot;</p>
<p>&quot;June Lynne, I received Mr. Forsberg's card from a librarian to
discuss Milburn's 'Flight Into Night'?&quot; I didn't mean for it to be a
question, but it certainly came out as one in that moment between
nerves and hesitancy.</p>
<p>The voice on the other end sounded excited and happy for me, &quot;Oh
excellent! Mr. Forsberg adores that book. Yes, Mr. Forsberg would be
delighted to extend you an invitation to meet with him here at his
residence to discuss it. Unfortunately, Mr. Forsberg keeps a very busy
schedule so it can't entirely be at your convenience. Mr. Forsberg's
calendar suggests that this Saturday Afternoon say at 3 PM would work
best for him, would you be available?&quot;</p>
<p>&quot;I, uh, yeah I can make that work.&quot; I did not expect things to move so
fast to a scheduled appointment, but had to admit I was free Saturday
night.</p>
<p>&quot;Excellent. I will let Mr. Forsberg know to you expect you here around
then. Anything else I can help you with?&quot;</p>
<p>&quot;I'm not sure I know what the address is?&quot;</p>
<p>There was a pause, then a hesitant, &quot;I believe it should have been on
the card?&quot;</p>
<p>&quot;Oh, well there's a street name but there doesn't seem to be a street
number.&quot;</p>
<p>&quot;Yes, that's the address.&quot;</p>
<p>That was not at all the answer I was expecting, but I was not going to
let my confusion trap me in a longer phone call, &quot;Alright, thank you,&quot;
and we exchanged the usual phone conversation ending pleasantries and
hung up.</p>
<p>That Saturday was warm, but not too warm, and overall a lovely bright
day. I was surprised that the maps app on my phone had no trouble
finding the address as I started to type it. As secretive as Mr.
Forsberg seemed to, only passing his address around on physical cards
it seemed, he couldn't stop it from being indexed into all the usual
databases. Though I appreciated that a lot in this case because I had
no idea how I would have found the address if it hadn't been
searchable on the phone app, I know I've become too reliant. I guessed
I'd try to call a librarian at that point, but I had a route and that
was a theory I didn't need to test.</p>
<p>The route took me down a city highway I've driven a million times and
yet somehow just two turns off it I entered what felt like a forest.
There was no room in my mental map of the city for this forest, and I
would imagine even a small park in this area would be something that
I'd recall noticing on maps before. The phone's satellite imagery
reassured me that this forest had probably been here the whole time
and it was maybe my mental map that needed updating, but these trees
felt too old and wild to be contained somewhere in a city's limits.
The street that was the entirety of the address was a small country
gravel road leading deeper into the forest. A country gravel road was
also something that didn't quite fit on my mental map of this part of
the city. It wasn't very far along the gravel road before it opened up
into a giant clearing. Here the satellite map showed a blur where the
clearing must be, in the center of the forest and I presumed that at
least some privacy could be bought even in the satellite age. Between
that and the number of buildings that were scattered across the
clearing I was starting to get an impression of an eccentric amount of
wealth involved (if I'd not already been making assumptions from the
card and the phone call).</p>
<p>The residence campus, and it seemed like a small college campus of
some strange sort, had a clear house looking building centrally beside
the gravel road, which seemed the most obvious place to stop and park.
My eyes wandered across the other buildings. They had a variety of
architecture styles and materials that seemed to imply an organic
growth over many decades. Only a few of them seemed to offer any
direct hints of their use. There was one full of windows and a lot of
fog on its windows yet greenery visible where you could see through it
implying a green house or conservatory of some sort. There was (if I
was counting the number of sides correctly) an hexagonal building with
an intriguing glass dome atop that I clocked as the library only once
I realized that seated on plinths flanking the entrance were
engravings of probably Athena or perhaps Minerva, marking it a hall
for wisdom.</p>
<p>I had barely mounted the steps towards the porch of main house when a
matronly stout woman bearing an iPad like a weapon arrived to
intercept me from further around the porch (which appeared to wrap the
entire house). With a deft, practiced maneuver she moved the iPad up
and under the crook of her left shoulder, and extended her right hand
in greetings, &quot;Ms. Lynne, I presume?&quot; I nodded as I took her hand into
handshake and she continued, &quot;I'm Mrs. Montgomery, Mr. Forsberg's
Chief Executive Officer, we spoke on the phone the other day. Mr.
Forsberg has decided to take tea in the garden on this delightful
afternoon,&quot; her handshake was firm and commanding, &quot;If you will follow
me, I will lead you to the garden.&quot; She broke the handshake,
unholstered the iPad and glanced at it, then proceeded to lead me
around the house.</p>
<p>From the first moment of meeting Mr. Forsberg I had a very tough time
deciding if he were some sort of monster or some sort of muppet. As he
unfolded himself from the garden chair, he kept seemingly growing
until he stood above me at what should have been an inhuman height. He
was tall and angular, yet seemingly far too skinny for his frame, in a
way that looked like someone had accidentally stretched a small boy
out longwise like pulling taffy. He was possibly the ugliest man I had
ever met in person, yet somehow one of the most charming as well.
There were hidden delights in the way his eyes glittered in the
sunlight, and every part of him animated with more life than the
average person seemed to. Somehow a part of me kept being surprised by
that over-exaggerated animation and it's surprising grace and fluidity
despite a seemingly very awkward and gawky frame making the movements
that much more implied to me that just out of range of my eyes were
marionette strings or a puppeteer's arm. They say that when you meet a
muppet like Kermit it's very easy to get taken into the illusion and
you don't want to see the strings or puppeteers after only a few
moments working with him, and not even moments into a handshake with
Mr. Forsberg I had decided that I did not want to dispel the illusion
and check to see if he was a muppet, even if I would constantly be
confused for the rest of my relationship with him that I would never
decide if he was more monster or muppet.</p>
<p>Mr. Forsberg invited me to take tea with him and within moments of my
acceptance and a light few presses by Mrs. Montgomery on that tablet
of hers, a cook had brought me a cup of Orange Pekoe and an assortment
of light cheeses and olives to accompany it. Somewhere in the middle
of pleasantries such as the lovely Saturday afternoon weather and the
beautiful garden around us, Mr. Forsberg offered, &quot;Please, call me
Xavier if you would like,&quot; and I was charmed by it in part because of
the somewhat incongruous feeling knowing his first name gave me. It
did not strike me as the sort of first name of either a monster or a
muppet, and maybe not even a first name I would expect from someone so
seemingly always formal by contagious force such as Mr. Forsberg.
Somehow settling into calling him Xavier did not dissuade him from
sticking to &quot;Ms. Lynne&quot; in all cases of address. It started to give me
the impression that &quot;Raconteur (Retired)&quot; was much less a
self-described affectation and somehow an accidental label given him
by some rival. I shouldn't have found it charming, but it came across
to me as very charming and somehow natural and native to Mr. Forsberg.</p>
<p>Eventually the general pleasantries gave way to the specifics and
Xavier asked how I’d become aware of Milburn’s “Flight Into
Night” and I mentioned that autobiographical footnote that had lead
me down the curious path to meeting Mr. Forsberg. Xavier nodded, and
admitted to having also read that autobiography. I mentioned my
thoughts from the time I read it that perhaps Milburn’s “Flight
Into Night” played a larger role in the context of the children’s
show and its origins than just a single footnote might imply. Xavier
nodded along and then congratulated me on my “astute
observations”.</p>
<p>We sat in comfortable garden white noise (some eager crickets, some
birds chattering sweetly among themselves). I realized I had finished
the available cheese and olives and my tea was finished.</p>
<p>“Well,” Mr. Forsberg cleared the silence somewhat hesitantly, “I
can tell you are fellow book reader after my own heart and I would
love to hear your book report on Milburns’s work once you finish it.
I’d love to give you access to my family’s library.”</p>
<p>Xavier suddenly had a key in the palm of his hand. I did not catch it
if he had palmed it from a jacket pocket from one of his big sweeping
gesticulations or even perhaps if some puppeteer’s stage hand had
placed it there. (Though I was only somewhat disappointed the key was
simply a standard modern Yale lock, not some monstrous ancient cast
iron thing or something even more fantastically fairy tale.)</p>
<p>Xavier moved to hand me the key but then paused, “I’m afraid I
must add one more stipulation to the terms: none of the books are to
leave the grounds. Though you will be treated as a very welcome guest.
You will be free to come and go as you please. I’m sure that Mrs.
Montgomery had already logged your vehicle into the security systems
and will only need a head’s up in the case you plan to arrive in a
different one. She can show you how to request tea service if you wish
for it, and I’m sure the chefs would be happy to account for you in
meal planning if provided enough lead time. I’m biased of course,
but I believe there are many fantastic spots, hidden and not so
hidden, to fall into a book throughout my homestead. On days such as
this, you might make use of this garden, of course, or there are
hiking paths out to some lovely little grottoes and scenic points. One
of my favorites has a surprising view of the downtown skyline. Indoors
there are of course study nooks throughout the library and I would be
happy to introduce you to some of the lesser used studies and sitting
rooms in some of the guest buildings if you want some variety.”</p>
<p>It was my turn to hesitate as that was a lot to take in. It certainly
was inconvenient, but there seemed to be far worse places to read
interesting books than gardens and library nooks where someone else's
job is to serve you tea. I also found that I was appreciating thanks
to such a rapid infodump how much Mr. Forsberg's hesitancy was perhaps
more an over-eager hope of and concern for being a good host than a
worry what sort of person he was giving access to his family's
library. I realized I had something of a term of my own as I thought
about Xavier's offer, &quot;I really appreciate the offer, Xavier. If I may
add a term of my own? If the book is what I've heard it to be, it
belongs in the public domain. I'd love to scan your copy to make it
accessible as an ebook for others that may not receive such a generous
offer from a wealthy patron.&quot;</p>
<p>There was a very complex wash of emotions across Mr. Forsberg's face,
too fast for me to catch any particular individual ones, but he
settled into a response I did not expect from someone of his presumed
wealth, &quot;Ms. Lynne, it would pain me greatly to let you volunteer such
labor when I can easily pay for it. I've brought in Ms. Hayward as a
consultant on several of my past library science needs and she's been
nothing but professional in projects such as that. But first, I would
certainly appreciate your opinion on that large <em>if</em> that you brought
up. It is a remarkable book in my opinion, but I shall await your own
book report to compare notes, Ms. Lynne. We can discuss things like
scanning that book after you've read it and given it time to digest,
yes?&quot;</p>
<p>Here again I found such an interesting and effectively charming
eagerness in Xavier's eyes especially. I nodded in agreement, it made
enough sense, and reached out my hand. He gently dropped the key into
my own hand and clasped my hand in something that wasn't quite a
handshake and wasn't quite a gentle pet, before just as gently
releasing my hand.</p>
<p>&quot;Wonderful, Ms. Lynne. I am eager to hear your book report. I've found
that first impressions of the family library are most interesting
without a formal tour. Perhaps when you are ready to deliver your book
report I'll explain a bit more and show you some of the library's more
hidden features. I can have Mrs. Montgomery show you to the library
building so you can get started.&quot;</p>
<p>I smiled, &quot;I believe it's the hexagonal building I spotted from the
street with the engravings of Athena watching its door?&quot;</p>
<p>&quot;Yes, of course! Very astute,&quot; and with that great smile of an
acknowledgement from Xavier, which I found strangely warming, I stood
and thanked Mr. Forsberg again for the opportunity to make use of his
personal library.</p>
<p>Standing just outside the library door I had a sense that the Forsberg
Family Library was probably bigger than my entire regular branch of
the free public library. (Though I liked that it was one of the
smaller branches.) The exterior door of the library lead into a very
simple vestibule. A couple of antique looking brass lamps lit up as I
opened the door and I presumed they were on a modern motion sensor.
Despite being a couple of relatively small lamps with modern LED bulbs
(I presumed) they did a great job lighting the entire vestibule. On
one side was a tiny cloak closet and the other an old fashioned card
catalog. Relatedly, a QR code had been printed and posted above the
card catalog. A note beside it that I recognized in Ms. Hayward's
handwriting stated simply &quot;Use QR code for other catalog services, but
please continue to use the card catalog as the main index. -V&quot;. I of
course knew the author I was looking for so I only needed the main
index, presuming it was sorted by author name. I was glad for having
had an eccentric elementary school teacher of mine force us all to
learn the basics of a card catalog, despite it being an incredibly
useless skill these days when all library catalogs were digital.</p>
<p>I opened the drawer that promised to contain Milburn, J. Y. and was
immediately drawn into how much of a colorful explosion the cards
were, and I wound up taking my time flipping through its contents in
search of the card I needed. Every card had the expected author name
and book title on the left hand side, and various other details such
as descriptions or author blurbs printed below those. Every card also
had at least a square of color in the upper right, like a test swatch
from a painting company. In a few cases the entire cards were painted
or printed the color of the upper right swatch. In some fun cases I
stumbled across the swatch expanded out into mini-paintings and
embellishments, such as on the card for Melville's Moby Dick where the
blue swatch color was part of a blue whale trying to swallow the book
description. Below the color swatches were decimal numbers, but they
certainly didn't seem to align with Dewey Decimal numbers in the
slightest. For one thing, even fiction works such as that Moby Dick
card I passed across had these decimal numbers, which Dewey himself
never bothered with, and for another the few Dewey Decimal categories
I could recall off hand when I saw similar numbers flip past did not
match the expected contents at all. The card for Milburn, J. Y &quot;Flight
Into Night— A Slumbering Fantasia&quot; was in the catalog right where I
expected it to be. It had a very interesting crimson/purple shade as
its color swatch, perhaps resembling the tones of the later parts of
dusk. A part of me wanted to keep shuffling through the cards to see
what other details might pop out at me, but I was also very curious
now to see the library itself.</p>
<p>With the book's card in hand I swung the door open to the library
proper. The vestibule door opened up into a massive flood of natural
light from the dome overhead. The vestibule felt claustrophobic in
comparison to the wall of light that spilled out from the building's
interior. That moment of transition took my breath away even before I
started to take in the contents of the library itself. The vestibule
hadn’t seemed that dark while exploring the card catalog, but it
felt like it took a while for my eyes to adjust to the much brighter
building interior. Even before the shift to lending mostly ebooks,
I’m not sure if my regular library branch ever had nearly as many
shelves as I could see in this private library. The hexagonal shape of
the exterior patterned the interior as well. All six interior walls
were lined with shelves of course, including the wall behind me with
the library’s entrance door buried in the center of shelves. Looking
towards the interior were concentric hexagonal rings of even more
shelves, slowly growing in height as they progressed toward the center
and an impressively looming central hexagonal spire of shelves. From
the entrance angle it felt like the shelves almost made stairs for a
giant to ascend to a throne just below the dome.</p>
<p>The shelves weren’t all entirely filled, presumably giving things
room to grow, but it was still impressive just how many books I could
see even before I started to explore the rings in more detail. The
most prominent feature of the shelves, though, was that the covers of
the books that shared each shelf all shared similar colors to each
other, with subtle gradient shifts across the shelves. Each triangular
sextant of the library was dominated by a single primary color, and
there seemed to be a pattern to the flow of the colors between and
among the rings. It was a beautiful color wheel that seemed to be the
central pattern to the library, though it didn’t seem to exactly
match any color wheel I was familiar with.</p>
<p>I could easily see why the swatch colors dominated the library’s
cards so much. Assuming you weren’t color blind I could tell that
you could track down individual books just by color sample alone as
you got used to how the colors shift across the color hexagons. It was
also quickly clear to me based on how the shelves were marked that the
decimal numbers were still very handy for finding books. The hundreds
place seemed to exactly match the sextants numbered 1 through 6. The
tens and ones places gave a sense of which ring, though it wasn’t an
exact or clear cut match. I had no idea what the decimals might
represent, but there were sometimes six or seven digits. Between the
decimal number and the color swatch it didn't take me hardly any time
to find the book I was looking for. It was between a treatise on art
deco architecture and a biography of a Russian playwright, and I
certainly had no more idea what the decimal numbers actually
represented in this library as there was clearly no categorical
organization here.</p>
<p>Other than the title and author on the spine of the cover I didn't see
any other marks such as a publisher's mark. As I pulled the book off
the shelf it became clearer that the dust jacket wrapped around the
book was a unique and custom laminated work of art. It had a
startlingly lovely dusk painted across it from back cover to front
cover. The setting sun was featured prominently on the back cover and
a kid whooping and hollering while riding a flying carpet took center
stage on the front cover. No other words or adornments on either back
or front covers except an artist's signature, &quot;R. Forsberg, Jr&quot;.
Inside the dust jacket, it was a classic dull brown leather bound book
of the expected sort for its age. I found I really appreciated the
beauty of the custom family cover, helping it to feel at home on such
colorful shelves. A manilla card holder was the only other addition to
the book, and one you would expect of a library. I slid the books card
into its holder, just as expected and wandered over to a nearby study
area to start into the book.</p>
<p>It took me several weeks of trips to the Forsberg Family Library to
finish Milburn's &quot;Flight Into Night&quot;. The prose was magical but
incredibly dense and slow to read. Over those weeks I became very
familiar with some of the reading spots in the library itself and in
the garden. I did manage to find the hiking path to the city skyline
overlook Xavier had mentioned. I had Mrs. Montgomery show me a lovely
sitting room in one of the guest buildings when I was craving an
indoor reading spot that was a bit more variety (and a little bit more
cozy and comfortable, like reading on the couch of a beloved great
aunt in her country cottage). I rather took for advantage the staff's
offer of tea service and was amazed at the selection and the variety
of charcuterie and tapas bites that would accompany the pots of tea at
chef's whim. I tried not to take advantage of the meal planning for
somewhat unreasonable fear of never leaving Mr. Forsberg's estate
again if I ate too much of its food. I was afraid of being
accidentally trapped as its prisoner (of exceedingly fine dining, as I
did find out the one weekend I requested it). Across those weeks I saw
Xavier himself only quite rarely and mostly in passing as he was off
to whatever kept him busy on weekends and I was sometimes directly
rushing off to finish the next chapter.</p>
<p>Milburn's &quot;Flight Into Night&quot; itself was equal parts amazing and
infuriating.</p>
<p>The book contained some amazingly imaginative descriptions of a dreamy
wonderland &quot;just beyond the clouds, up the stairs, through the crystal
tunnel, then across the dusk ocean, but only when you are asleep&quot;
named &quot;The Far Kingdom&quot;. The protagonist was a tween boy with a love
of adventure and maybe just a bit of narcolepsy, and of course the
adults in his life didn't believe such a wonderland existed, much less
were they interested in helping the poor protagonist in sorting out
the various and sundry political conspiracies threatening to blow up
The Far Kingdom and maybe even the &quot;bland lands&quot; &quot;below&quot; such as the
protagonist's home and all those adults that didn't trust him and
considered him a sad little boy with a mental illness. At the end
about as you would expect the kid solves the political conundrum in a
strange but fun way, wins the interest of the Princess of The Far
Kingdom, and maybe manages to wink-nod earn some respect from the
adults in his real life with a trinket he brings back as the Hero of
the Far Kingdom. Like some of its other near contemporaries such as
<em>Peter Pan</em> or <em>Wizard of Oz</em> or <em>Little Nemo in Slumberland</em> it was
pretty tightly focused on the protagonist's view point and treated the
adventures as very real and quite serious. Like Oz the world seems
setup to be a possible reusable franchise, way back before that was
anywhere near a common thing, though the card catalogs in both the
public library and in the Forsberg Library seem to imply that any
proposed sequel was never written. The obvious reason to assume no
sequel exists is purely economic: the book sold only reasonably well
regionally and the publisher went bankrupt soon after, but I was also
starting to see how some of the more problematic elements of the book
may have also prevented J. Y. Milburn from attempting any longer term
plans with the material.</p>
<p>It was in its problems that the book was incredibly frustrating. For
all of its wonder and twee imaginative descriptions it also contained
deep wells of intentional and accidental racism. The politest
sentiment you might often hear here is that the book was &quot;extremely of
its time&quot;, but the view of the book from this particular modern
vantage point seemed to say that maybe it was even worse than its
time. The use of a flying carpet as the primary transport to, from,
and around The Far Kingdom easily implied that Milburn had studied
Arabian Nights and possibly a wealth of other multi-cultural artifacts
and just about every time you started to feel like perhaps the
appropriation was unintentional Milburn introduces yet another racist
caricature or veers rather closely to outright islamophobia or
something worse. Most uncharitably the book is a white savior myth of
a boy colonizing his dream space and winning the &quot;exotic&quot; princess as
a prize for his efforts. Both things are true at once: it's an
imaginatively charming boy's adventure and it's a mess of bad
stereotypes and outdated views and things that are very clearly awful
tropes from a modern lens. Can you forgive the awful parts for the
charm of the good parts? Can you blame a book for perpetuating bad
stereotypes and tropes when historically it may have predated or even
been the cultural source of some of them? I didn't have good answers.
I enjoyed reading the book for the most part, and I enjoyed reading it
in relatively disconnected from the real world context of the Forsberg
Family Library (and its garden and hiking paths and guest sitting
rooms).</p>
<p>When I finished the book I asked Mrs. Montgomery to please schedule
some time to chat about it with Mr. Forsberg. Xavier insisted on lunch
together and I found myself unable to turn down an invitation for
another amazing meal from his chefs.</p>
<p>Over lunch we mostly discussed pleasantries. It was charming and
lovely and the food was as spectacular as I figured it would be. Over
a slice of cake and coffee the conversation finally turned to Milburn
and &quot;Flight Into Night&quot;. Mr. Forsberg asked for my report and I found
to my surprise I had a lot to say about it. Enough that the
conversation flowed back out into the garden and into some afternoon
tea. I enjoyed discussing all of the good parts of the book that
captured my imagination. I got somewhat heated discussing the book's
problems and how upsetting some of it had been to read in this decade.
I mentioned my gut instinct that Mr. Milburn was probably even worse
than average of his own time period and it wasn't entirely &quot;it was a
product of its time&quot;, the theory that some of its mean spiritedness
and racism was so well read as to be intentional in its appropriation.</p>
<p>As conversation had subsided and with it again the tea and the various
fruits that had accompanied it today (despite how much we ate for
lunch, the bits of fruit felt cleansing and powerful when accompanied
with a delightfully floral green tea), Xavier finally asked the
question I had been dreading, &quot;I'm glad that you enjoyed the book, and
I've appreciated your perspective on its many problems. Given what you
know now of this book, would you still wish to recommend that I pay
someone like Ms. Hayward to scan and OCR the book for consumption by
the internet as an ebook?&quot;</p>
<p>I had been asking myself the same thing for days. I had been at war
between the parts of me that were a big proponent of the public domain
and keeping access to interesting cultural artifacts (especially in an
age when so much of what should be the public domain was gated by
leaseholders demanding rent and we could use all the public domain we
could get), and yet the parts of me that admired it as an interesting
cultural artifact that is so terribly flawed it probably should never
be available in the context of the internet. &quot;No, probably not. I mean
it–&quot; was about all I got out before my brain's own infighting
stopped me short. After hours of talking about the book, talking about
what to do with its future choked me up a bit.</p>
<p>I wasn't sure how much of that Xavier caught nor how much he felt
himself, but he stood, unfolding to forbidding height in the process
as usual, and offered a change of topic, &quot;I offered you a personal
tour of some of the secrets my family's library in exchange for your
book report, would you like to join me?&quot; He offered his hand to me,
and I accepted it.</p>
<p>&quot;My father considered himself a failed painter,&quot; Xavier started as he
lead the way in a direction I felt pretty familiar with, &quot;He went to
art school against his father's wishes, was never commercially
successful, and eventually returned home. I'd submit that my father's
view of success was myopic, but I'm biased, in part because our
library was my father's greatest painting. Like many of the greatest
works of art it was never completed in my father's lifetime, but like
only a few truly great works of art this was because it was designed
to be a never ending, living installation.&quot;</p>
<p>We started with a lap around the exterior of the building. Mr.
Forsberg was full of facts about little details and secrets his father
had hidden in the architecture, of which his father had first draft,
but it sounded like he'd contributed his own share of details. I have
no real knowledge of architecture with which to appreciate most of
such secrets, but Xavier kindly pointed out that some of the books in
the library could provide additional context if I wanted it. (Though
I'd probably need to look them up via Ms. Hayword's digital secondary
indexes.)</p>
<p>Stepping into the vestibule, after a few fun facts about the brass
lanterns and seeking out a custom LED manufacture for their unique
sockets, Xavier pointed out what I had come to assume about the card
catalog, &quot;My father of course knew about Mr. Dewey's system and he
hated it. He thought that it removed the creativity of unusual
juxtapositions, as you can maybe now guess. He grew up with my
grandfather's random shuffled library, with books in shelves spread
haphazardly throughout the house, and as much as he loved the
unorganized mess he wanted to organize it somehow. He considered Mr.
Dewey's system boring and problematic for the ideas in books to only
be in conversations with each others in the same 'intellectual
category'. Most importantly he thought it robbed books of their
context: pigeon holing books into strict categories implies that
everything about those categories can be exclusively found in those
and only those books, that every book in a category is equally
valuable to current thought on that category, and that perhaps worst
of all the impression of full shelves in a category can give the
impression that everything has already been learned and written down
in that category. My father wasn't trained in the library sciences, he
was a failed painter, so he used the tools that he knew: colors. Then
he arranged them into numbers that made sense to himself and thought
to arrange books by the most interesting color on their spines.&quot;</p>
<p>I nodded along at that, and even knew what to fill in of the next
detail thinking of the delightful dusk colors of the cover on
Milburn's &quot;Flight into Night&quot; and the &quot;R. Forsberg, Jr.&quot; signature on
it, so I added, &quot;And where books didn't have interesting colored
covers, he painted some beautiful ones himself.&quot;</p>
<p>Xavier clapped in delight at my observation, &quot;Indeed. Books were
always my father's greatest muse and organizing the family library
gave him decades of reasons to paint beautiful covers for old books
that needed new or different contexts.&quot;</p>
<p>He opened the door into the main hall of the library and ushered me in
before continuing, &quot;Of course, a lot of them needed new contexts,
including Mr. Milburn's <em>Flight Into Night</em>. My grandfather and my
great grandfather were quite the collectors in their respective times,
and as you may suspect of nearly any family of multi-generational
wealth in America such as mine we had more than our fair share of that
made on the backs of other people and some of that was intentionally
more than a little racist. My father didn't believe in hiding the
skeletons in the closet, but bringing new contexts and bright shining
light to the horrors, as a reminder and a caution for future
generations.&quot;</p>
<p>Xavier ran a hand over a seeming random shelf and just about
immediately spotted an example, sliding it off the shelf. It had
fascinating bright cover, but intentionally no words on the spine. The
dust jacket cover was another &quot;R. Forsberg, Jr.&quot; original, only this
time a gathering of ghosts with shocked and angry faces in horror. Mr.
Forsberg slipped the laminated dust jacket off just enough to reveal
an old leather bound volume with a long disgustingly clinical title
involving &quot;trepanning&quot; and that was more than enough than I needed to
know for why those ghosts were horrified, angry, and shocked. Xavier
shivered in a shared horror of his own, replaced the dust jacket, and
then reshelved the book. He pointed to its neighbors, &quot;Today such a
horror has as its primary chatting companions a field guide on birding
and a novel about teenage self-esteem. Perhaps it will learn from such
better neighbors. I've had Ms. Hayward project my father's
idiosyncratic system to Mr. Dewey's and even modern color numbers such
as HSL and CIELab, but the idiosyncratic system is what makes this
library the strange incomplete work of art that it is.&quot;</p>
<p>I felt like I was still reeling at the given &quot;random&quot; example, &quot;Why
wouldn't you burn a book that awful rather than shelve it?&quot;</p>
<p>Mr. Forsberg's eyebrow quirked up, &quot;Weren't you the one trying to
convince me of the value of old books to the public domain as a term
of accessing this library?&quot;</p>
<p>I sharply inhaled as if hit. He was obviously right, and I suppose
that now I had a lot more context to work on, including the racism I
had directly combated in my reading of Milburn's &quot;Flight Into Night&quot;.</p>
<p>Xavier smiled and there was a kindness I didn't expect or know what to
do with in his muppet of a monster's face after such a surprising
insult, &quot;Come, please. Allow me to show you my favorite secret in the
library.&quot; It dawned on me that this wasn't the first time that he had
had a conversation such as this, and that as embarrassed and pained as
I felt from his response, I had a lot more reason to understand that
complex wash of emotions when I had so casually discussed scanning
books for posterity. This family's library was for a different sort of
posterity, as it collected as much of the bad as the good.</p>
<p>He lead me to the opposite wall in the hexagon from the vestibule
door. He ran a hand along the shelves looking for a particular volume
and it shouldn't have surprised me when pulling it off the shelf
revealed a hidden button behind it. Pressing the button made the sound
of a great mechanical &quot;kerchunk&quot; of presumably a locking mechanism and
then just as smoothly as Xavier replaced the book on its shelf an
entire section of shelf next to that book slid out on rollers as a
hidden door. Behind the door was a simple flight of stairs running up
from the main hall of the library. I spent so much time in the library
and I would not have suspected a hidden door or that the hidden door
might have stairs leading up of all places. I eagerly followed Mr.
Forsberg up the steps to see where they lead.</p>
<p>&quot;Oh wow,&quot; I stated overwhelmed and feeling like such words were not
near enough as I stepped out from the stairway into a gallery tucked
under the dome. Below the gallery you could see the entire color wheel
(color hex) of the library laid out before you as a beautiful rainbow
room. I wasn't sure what sort of magic angles had been applied to the
shelves below to deliver the &quot;trump l'oiel&quot; special effect below that
made it feel to me like I could see all the colors of the covers of
the books from no matter what angle I was looking down from as I
gently wandered the circular gallery.</p>
<p>Xavier chuckled at the way I was straining my head around trying to
see if I could catch the trick of it, and he gave me some time before
finally filling the space with the rest of the conversation as if it
hadn't been interrupted by beauty below our feet, &quot;This is the big
reason I felt I must insist the books don't leave the family's
grounds, this final overhead context. Just as culture itself tries
piece by piece to make the best things it can out of the best parts of
things that came from before it by recontextualizing them, this
library is one grand work of recontextualization. Mr. Milburn's book
inspired a jazz quartet to create a lovely dream suite that in turn
was sampled by some of the hip hop classics. Mr. Milburn's book inspired a
progressive children's television show that tried to equally celebrate
children's imaginations while also encouraging diversity as the way to
do it, rather than an obstacle in the way as Mr. Milburn seemed to
believe. Mr. Milburn's work is better for how culture already moved on
and readapted the good parts, leaving bad ones behind. So too, Mr.
Milburn's work is better as one part of a prism's rainbow in a library
where new contexts matter more than old ones.</p>
<p>&quot;My father insisted on the big natural light dome and that the best
prisms came from sunlight. The best way to fight darkness in our
hearts, in our culture's past, in our family's wealth, was to shine
not just any light on it, but sunlight. My father knew full well that
the sun would over time paint its own brushstrokes of fade patterns
across the colors in the canvas below and that sort of collaboration
was something my father always trusted, and that too felt symbolic to
my father.&quot; I noticed the streaks of tears on Xavier's face, and felt
more than a little moved myself. There didn't feel like much to say in
response while I digested that. I continued to take in the sight of
the library below me and we took our time before we left the gallery
for supper.</p>
]]>
      </content:encoded>
      <category>fiction</category>
      <pubDate>Thu, 16 Nov 2023 00:00:00 GMT</pubDate>
    </item>
    <item>
      <title>One Last Feature for Pharkas Component Framework (for Angular)</title>
      <link>https://blog.worldmaker.net/2023/09/05/pharkas-complete/</link>
      <guid isPermaLink="false">https://blog.worldmaker.net/2023/09/05/pharkas-complete/</guid>
      <content:encoded>
        <![CDATA[<p>With version 6.0.0 I released one
<a href="https://blog.worldmaker.net/2023/09/05/pharkas-complete/#the-state-of-pharkas-in-late-2023">last</a> feature for the
<a href="https://worldmaker.net/angular-pharkas/">Pharkas Component Framework</a> and with version 7.0.0 I
updated it to the Angular's current LTS (15). Pharkas is a mature,
battle-tested library for writing user-responsive UI components
using RxJS best practices in Angular. These two updates are likely
my bow around my involvement in the project.</p>
<h1>A Brief Intro to Pharkas</h1>
<p>Pharkas came from <a href="https://blog.worldmaker.net/2021/06/26/angular/">my frustrations with Angular</a>, especially
with how many core components of Angular may unintentionally lead to
breaking RxJS best practices. At that point I had a nascent idea of
a framework for better RxJS practices in Angular, which I briefly
nicknamed <a href="https://blog.worldmaker.net/2021/06/26/angular/#suggestions-for-a-better-angular-project-gawky">&quot;Project Gawky&quot;</a>.</p>
<p>A few months later <a href="https://blog.worldmaker.net/2022/10/30/angular-components/">while debugging and fighting a Halloween sort of
ghost story in Angular</a> caused by Angular's default change
detection system and its (over-)reliance on Zone.js, I felt pushed to
reevaluate the &quot;Project Gawky&quot; ideas in a concrete base library that
could be used in brownfield applications as a way to make it much
easier to adopt Angular's more civilized <code>OnPush</code> change detection.
(At least from a &quot;bottom up&quot; direction, as default components can
use <code>OnPush</code> ones easily but vice versa isn't as possible or useful
in Angular.)</p>
<p>Pharkas defines something of a DSL for component building in Angular
that is entirely dependent on RxJS Observables, entirely focused on
working solely with them to drive a component's behaviors (including
life cycle), and built to make it easy to follow RxJS best practices.
These include helping developers to avoid over-relying on Subjects,
and minimizing calls to Observable <code>subscribe</code>, and especially
attempting to eliminate calls to <code>subscribe</code> without a corresponding
<code>unsubscribe</code> cleanup at the right component life cycle point. The
benefits to following these best practices (as opposed to many
&quot;best practices&quot; Angular developers often are exposed to in the wild)
should always be a reduction of memory leaks.</p>
<p>Pharkas takes this further by automating the somewhat manual <code>OnPush</code>
change detection in Angular. Change detection is handled entirely
based on Observable wiring. Pharkas is designed to lead the component
developer into <em>user-responsive</em> components by default.</p>
<p>Pharkas' &quot;DSL-like&quot; patterns have been occused of being a bit
verbose (an unfortunate side effect of Angular's use of non-standard
decorators), but in practice I've often found component code to be
smaller and easier to read when written with Pharkas. Once you
factor in the easy wins from Pharkas' smarter out of the box change
detection behaviors, full mostly automatic component life cycle
management, and other subtle performance benefits, I think the
&quot;verbosity&quot; easily seems to disappear and Pharkas for became the
only good way to write Angular components.</p>
<p>I think Pharkas makes the best way to write components when
performance matters. I think Pharkas also excels at writing thinner
wrappers around Vanilla JS components avoiding unnecessary Zone.js
overhead taxes across the component boundaries. (Vanilla JS
components that handle all their own DOM don't need Zone wrappers
and with Pharkas wrappers will never signal extra busy work to
Angular's change detector.)</p>
<h1>Basic Suspense (The Last Pharkas Feature)</h1>
<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
product managed to in general meet most of the expectation (outside
of being directly a template language dialect rather than a DSL
inside component constructors). These ideas came largely from having
watched a lot of what React had been doing, very slowly, across
multiple major releases. I felt like a lot of those same benefits
could come from an RxJS-first approach, as much of it looked like
built-in schedulers and common operator patterns.</p>
<p>From the beginning Pharkas has made easy in Angular to do a lot of
the complex timing management that React calls &quot;Concurrency&quot;:
any Observables bound to a template with Pharkas
<code>this.bind(name, observable, default)</code> will notify Angular's
change detector no faster than <code>requestAnimationFrame</code> time. This
keeps components highly user-responsive, as the browser will
throttle <code>requestAnimationFrame</code> time as necessary to focus on
user interactions and keep the UI responsive. (A browser may also
reduce <code>requestAnimationFrame</code> time when a tab is hidden or
backgrounded, further improving battery efficiency.)</p>
<p>(Of course, user-responsive also means that sometimes you need
changes propagated as soon as possible, and Pharkas also has
from nearly the beginning provided
<code>this.bindImmediate(name, observable, default)</code> to force immediate
change detection on observations. In React this sort of thing is
needed especially for form elements when the &quot;source of truth&quot; is
the virtual DOM; form updates need real time to avoid upsetting
users. In Angular, Reactive Forms treat the real DOM as source of
truth and I've found the need for immediate bindings in Pharkas
extremely rare.)</p>
<p>As I had surmised, this is easily accomplished by a relatively
simple RxJS operator and scheduler combo (it is about entirely
just <code>debounce(0, requestAnimationFrameScheduler)</code>) when you, like
Pharkas, are assuming entirely Observables (and nothing but
Observables), and are using Angular's <code>OnPush</code> change detection
strategy.</p>
<p>The other side of the React example over multiple major versions
was what React calls &quot;Suspense&quot;. I had mentioned that among the
&quot;Project Gawky&quot; ideas, but hadn't implemented until now. In Version
6.0.0 Pharkas learned a very basic version of &quot;Suspense&quot;: a component
may <code>this.bindSuspense(suspenseObservable)</code> to determine when to
raise a flag that the component is suspended. While suspended,
Pharkas will suspend all further change notifications to the Angular
change detector until the flag is lowered.</p>
<p>One motivation for a suspense observable is loading situations where
you want to display a simple loading UI and fewer of the intermediate
template states while it is loading. Another possible motivation
would be situations where you may expect a lot of expensive
calculations and want to avoid browser DOM work while it happens.</p>
<p>It's another (final) tool in the user-responsive toolbox. Immediate
bindings still trigger change detection, and Angular will do
(opaquely) choose to do change detection on its own every so often.
It won't entirely eliminate &quot;intermediate&quot; states in your UI, but it
will certainly provide a knob to tune refresh rates in periods of
application time where there are other priorities than DOM updates
of your component.</p>
<p>It's not quite apples-to-apples with the full power and complexity
of React's Suspense, but it's an interesting, basic relative that
can deliver similar experiences in similar use cases.</p>
<p>It's also nearly as &quot;simple&quot; from an RxJS standpoint under the
Angular <code>OnPush</code> regimen in Pharkas: effectively just an extra
<code>combineLatest</code> and <code>filter</code> added into the change notification
pipeline. (It's also &quot;pay to play&quot; and if no suspense observable is
bound, these additional pipeline steps aren't added.)</p>
<p>It's not very discoverable, but you can see it in action in the
<a href="https://worldmaker.net/angular-pharkas/demo/index.html">Pharkas demo</a>. If you click the test component with a counter
the demo will now toggle suspense for that component. You may use the
embedded <a href="https://github.com/cartant/rxjs-spy">RxJS-spy</a> in your Dev Tools console to verify that the
timer observable updating the counter continues to fire on its usual
schedule and that the demo isn't cheating in this &quot;loading&quot; suspended
state.</p>
<h1>The State of Pharkas in Late 2023</h1>
<p>Pharkas is mature, stable, and now feature complete with respect to
my original vision for it. It is up to date with respect to current
Angular LTS (15).
<a href="https://github.com/WorldMaker/angular-pharkas">Pharkas is MIT licensed open source on GitHub</a>.</p>
<p>I suspect this Suspense feature will be the last feature for Pharkas,
for several reasons: Because it feels feature complete and because
I have no current expectations to continue working with Angular.</p>
<p>Feature-wise, I think the one thing left that bugs me a small bit
still is that I would love to make the DSL prettier with even more
meta-programming should Angular ever finally stop using non-standard
decorators so much. Maintenance expectations for any Angular library
are of course constant updates to keep up with LTS. That will never
likely be &quot;complete&quot; given the way Angular compatibility tends to
work and the complicated nature of Angular's peer dependencies in
npm. I expect maintenance to be needed, but I don't expect to do it
at this point.</p>
<p>In large part this is mostly because I was recently let go from my
job of the past 8 years and am looking for new opportunities and
challenges. I don't have any Angular apps to maintain now and I
don't know if I can entirely avoid Angular in whatever my next
opportunity is, but I know I'm not going to be especially looking
to continue working with it. (It almost drove me crazy.) Without
someone paying me to maintain Angular apps, my interest in keeping
up with the Angular maintenance treadmill falls off a cliff.</p>
<p>I think Pharkas is mature and stable. I believe in open source, and
will entertain suggestions for new maintainers of the project. In the
meantime, I will try my best to welcome pull requests and run
maintenance tasks if politely asked.</p>
]]>
      </content:encoded>
      <category>coding</category>
      <pubDate>Tue, 05 Sep 2023 00:00:00 GMT</pubDate>
    </item>
    <item>
      <title>Playing with the Bones of Star Trek: Picard</title>
      <link>https://blog.worldmaker.net/2023/04/29/picard/</link>
      <guid isPermaLink="false">https://blog.worldmaker.net/2023/04/29/picard/</guid>
      <content:encoded>
        <![CDATA[<p>The other day on Mastodon I got to thinking and talking again about how some of the
ideas of <em>Star Trek: Picard</em>'s first season really with resonated me, but that it was
mostly a problem of execution of them to me, and that was part of why I watched the
full of its second season despite disliking so much of it. (I haven't finished watching
the third season, and am not sure if I will, but I did read all the spoilers for it.)</p>
<p>I'm going to lead with this: The key motivation for me was how important I think the
Borg <em>Coöperative</em> are to Star Trek and why I loved the <em>idea</em> of late in life (ex-)
Admiral Picard being the only diplomat who could &quot;properly&quot; welcome them to the
Federation. A bunch of the seeds were laid in Season 1 for the Coöperative, and then
Season 2 entirely ignored them to do its own weird thing, in the process messing up some
key parts of the Coöperative (not just in a &quot;that's not <em>my</em> canon&quot; nitpicky way but
missing some of the underlying raison d'etre and character motivations, including
unthethering it from the &quot;Prime&quot; timeline altogether in a &quot;we don't even trust it to <em>be</em>
canon&quot; sort of way).</p>
<p>It feels important to lead with motivation like that, because that's going to be a
running theme in what follows.</p>
<p>(Also, I'm using the rare-to-English diaresis for the word &quot;coöperative&quot; in this
article mostly because it is fun and I can pretend to be high society like <em>The New Yorker</em>,
but also partly because it is fun how it can be confused to be Swedish enough to be a
&quot;Borg umlaut&quot;.)</p>
<h1>A Brief History of the Borg Coöperative</h1>
<p>A quick informational aside: Star Trek canon is primarily broken down into three main
wiki databases. Memory Alpha tracks TV and movies, Memory Beta tracks video games and
novels, and Memory Gamma tracks everything else from comics to toys to all sorts of
weird tie-ins with a vague notion of continuity. So terms like &quot;Alpha canon&quot; are a useful
shorthand for &quot;Star Trek stuff that happened on the TV screen or a movie screen&quot;.</p>
<p>In Alpha canon prior to <em>Star Trek: Picard</em> there is one single, mostly standalone
<em>Star Trek: Voyager</em> episode teasing the idea of the Borg Coöperative. The basic concept
is that which is implied by the name: a group of Borg, disconnected from the <em>Collective</em>
(we all know and &quot;love&quot;), decide to try to build their own culture with something more
in lines with Federation ideals and as a &quot;worker-led coöperative&quot; where they could elect
their own leaders and strive for a better Borg society.</p>
<p>In the classic fashion of Voyager, the idea is never revisited, there's no ongoing plot
threads from that episode, there's no idea what happened to those people and that
&quot;movement&quot;, it was just one mostly self-contained episode among many.</p>
<p>So, of course, being a good idea with interesting repercussions, Beta canon had a blast
with it for a couple of decades. I mostly am aware of how <em>Star Trek Online</em> portrays
the Coöperative, but I read several of the key novels as well, for various reasons. The
basic throughline I think is pretty obvious: in the Delta Quadrant power vacuum left by
the death of the Borg Queen and infectious destruction of a lot of key Borg communication
channels, the nascent Coöperative of that lone Voyager is given a massive petri dish of
former Borg Collective toys with which to grow and peacefully expand their culture. Over
time, depending on source/take, even notable figures such as Hugh and Seven of Nine serve in
various <em>elected</em> positions in the Coöperative (Presidents and Councilors and such),
further enriching the knowledge transfer of Federation ideals, until eventually the
Federation recognizes the Coöperative as proper <em>allies</em> (including in the fight against
still deadly remnants of the Collective) and then eventually <em>members</em>.</p>
<p>The Coöperative is such a fun idea to Beta canon because the irony is never lost on most
of the Beta writers that the Collective is frightening for how <em>quickly</em> they assimilate
other species, but the Collective is <em>not</em> the greatest assimilation force known to the
Star Trek galaxy: the Federation is <em>much</em> slower at assimilation, but the history of
Star Trek suggests it is more deeply the winner. The Borg Collective gets a couple of
interesting battles deep into the heart of the Alpha Quadrant but mostly fails to get
a lasting beachhead, stymied time and again (and back in time) by the Federation.</p>
<p>The Federation frees what seems like only a few dozen Borg total, infects them with
Federation <em>ideals</em> and virtues, sends most of them along on their merry way with
seemingly zero after-care or oversight, and <strong>most</strong> of that was done by a <em>single</em>
ship <em>lost</em> in the Delta Quadrant, the Borg Collective's home turf, that was barely
a threat (though did get time paradox lucky to eventually kill the Queen, but it
was either that or more years in hell, so Captain Janeway felt free to violate
several sections of the Temporal Prime Directive for that outcome in the Prime
timeline). It wasn't an attack force. It wasn't a planned strike. It was one silly
ship <em>talking to people</em>.</p>
<p>If you've been following along in Alpha canon, that irony of the Borg Coöperative
being an <em>accident</em> of infection of Federation ideals shouldn't be a surprise. The
Alpha canon is full of Federation &quot;exceptionalism&quot; like that. Time and again, the
many shows suggested that the Federation was more enlightened than everyone else and
concepts like democracy and Infinite Diversity in Infinite Combinations would always
win in the face of just about anything. There's something delicious about that
winning even against the Borg Collective's culture, even if some of it was an
accident and all of it was a very slow form of assimilation.</p>
<p>Beta canon followed that to some of its logical extremes: that picture of Borg
Coöperative members serving in Starfleet as another &quot;species&quot; adding diversity of
thought and culture to the Federation.</p>
<p>I personally love that. That's my Star Trek.</p>
<h1>The Immediate Problems with <em>Star Trek: Picard</em></h1>
<p>I'm going to get back to the fun shortly enough, but I feel in the interest of
context I briefly need to mention some of the biggest shortcomings I saw in
<em>Star Trek: Picard</em>.</p>
<p>I think it is clear at this point that <em>Star Trek: Picard</em> put the Borg Coöperative
onto the tee, went to swing at it, and entirely whiffed it.</p>
<p>It's <em>implied</em> but never actually text in any season of <em>Star Trek: Picard</em> that
the Borg faction that bookends Season 2 is even the Borg Coöperative. It seemed
obvious to me, with my exposure of Beta canon, but it has been the source of a lot
of Season 3 confusion.</p>
<p>The other thing that bugs me the most about how Season 2 presented their origin of
an Almost-Coöperative is that it misses some of those key bits of Federation ideals
that make not just the Beta canon Coöperative but also the brief, nascent
Coöperative that Voyager briefly interacted with in a single episode. The
Almost-Coöperative is presented as having a traditionally symbolized Queen and
while they request to join the Federation don't seem to have <em>internalized</em>
quite so much of Federation ideals.</p>
<p>Though admittedly a lot of that is still <em>implication</em> and <em>supposition</em> because
there is not enough <em>text</em> there in Season 2. Maybe Agnes there is just an
elected Ambassador chosen based on a bunch of information, and not actually a
Queen. We have basically no idea because they showed us the &quot;twist&quot; that it
was Agnes then didn't really have time to <em>tell</em> us anything about that. Twist
done, the show decided the only thing it really had left to do that season was
roll the credits credits.</p>
<p>That leads into what I think were the largest problems of <em>Star Trek: Picard</em>:
&quot;No tell, only show&quot; and &quot;twists/late infodumps over exposition and character
motivation&quot;. Neither of these problems originate with nor are unique to
<em>Star Trek: Picard</em>. One is just kind of an accidental product of post-<em>Lost</em>
media. Everyone <em>knows</em> &quot;People love twists!&quot; Everyone forgets that we used
to generally know character motivations as an audience and have some semblance
of reasons to watch a given scene rather than assume it <em>might</em> be relevant
later, maybe, if the &quot;twist&quot; is right and it wasn't a red herring.</p>
<p>The &quot;No tell, only show&quot; seems like the obvious pendulum extreme affecting
almost all of current &quot;prestige television&quot; and precisely what you'd get
from a generation and a half of writers drilled on &quot;Show, don't tell&quot;.
&quot;Show, don't tell&quot; comes from a good intention. It's meant to spark
creativity. But it's a pendulum and both extremes are painful. Too much &quot;tell&quot;
and not enough show and the audience is bored because they are getting
didactically lectured at with nothing interesting to see. Less appreciated is the
other extreme: too much &quot;show&quot; and not enough &quot;tell&quot; is a recipe for boredom
in its own way. Things that might have taken a two minute conversation take
20 minutes of scenes across two or three episodes. Without knowing their
greater context in the scheme of things those scenes can feel like complete
wastes of time for maybe a brief &quot;oh that's what <em>that</em> was about&quot;. (Which
is easily confused for &quot;twists&quot; and exacerbates and feeds the other problem.)</p>
<p>To me <em>Star Trek: Picard</em> is one of the most emblematic shows in all of
television for &quot;No tell, only show&quot; especially because we have the context
of <em>Star Trek: The Next Generation</em> (and TOS, Voyager, Enterprise): <em>so many</em>
TNG episodes are an amazing balance of tell and show. <em>So much</em> of Star Trek
storytelling is dropping the right &quot;Captain's Log&quot; at a good moment to quickly
push the audience on to the next thing they need to know.</p>
<p>The combination of these problems leads me to this feeling like most of the
story of <em>Star Trek: Picard</em> was told &quot;backwards and upside down&quot;.</p>
<h1>The Rearrangeable Bones of <em>Star Trek: Picard</em> Season 1</h1>
<p>I have this impression that you could especially take the first season, &quot;simply&quot;
reorder it, and tell the same story <em>better</em>. That if you focused on getting
more motivations up front, rather than saving things for &quot;twists&quot; that don't
quite impact because you had no idea of character motivations. Especially, if
we assume that one of the end goals, because of the second season bookends,
<em>was</em> to set up seeds for the Borg Coöperative.</p>
<p>I have this gut feeling that most of what follows could possibly be done in an
edit bay with a talented Editor using just the existing show as it was and
<em>maybe</em> a choice &quot;Former Admiral's Log&quot; voice over here or there.</p>
<p>Here's what I imagined this could play out (the other day, mostly
extemporaneously on Mastodon; I'm sticking to mostly light edits, plus
new asides):</p>
<ul>
<li>In the deep past Romulans discover Mass Effect’s Reapers also exist in
Star Trek</li>
<li>This drives Romulan paranoia in general. The Romulans build a cult around
this and also make it a founding mission of the Tal Shiar to destroy synthetic
life before it starts</li>
<li>This kind of explains why a species with the science of Vulcans but fewer
morals ultimately creates/enslaves the Remans rather than builds robots</li>
</ul>
<p>Dropping in a quick aside here, to break the bullet point rhythm, it's still
an incredible shame that given the events of <em>Star Trek: Nemesis</em> that not
a single named Reman existed in <em>Star Trek: Picard</em>. Since I'm busy armchair
quarterbacking my way through a rewrite of the show anyway, I'd have shown
the Qowat Milat to be primarily Reman and a <em>Reman Unificationist</em> group.
&quot;The Way of Absolute Candor&quot; versus the culture of secrecy and lies of Romulus
prior to the Hobus explosion seems almost <em>obviously</em> an outside force and
certainly in my headcanon easily sounds like a Reman concept. (Sure, it is
implied to mean more like a Romulan counter-movement/&quot;Buddhist reformation&quot;
of Surak's teachings on Vulcan, and &quot;absolute candor&quot; in emotions primarily,
but you don't just drop that title in the middle of the Tal Shiar-obsessed
Romulans and expect it to not also imply fewer secrets and lies.) Probably
Elnor should have been a Reman. That's about all I plan to say about Elnor.</p>
<p>Back to the story reorder in progress:</p>
<ul>
<li>Romulan Tal Shiar infiltrate Starfleet top brass (because of course they
do, Tal Shiar!)</li>
<li>Romulans blow up Federation synthetics and blame it on Synthetics going
rampant, just like all those old Earth movies predicted</li>
<li>Nobody catches this but the ex-Tal Shiar love interest of a retired
Admiral who lost all his political power trying to save Hobus explosion
(Romulan destruction) escapees and largely failed</li>
<li>No one believes the nutty anti-Romulan conspiracy theory from the fuddy
pro-Romulan ex-Admiral</li>
<li>Having “gotten away with it” the Tal Shiar move on to their next priority
target acquired from Starfleet intelligence: a derelict Borg cube
reclamation process</li>
<li>The cranky old Admiral convinces a weirdo civilian and small weird crew to
drive him there to that cube to desperately catch the Romulan spies in the
act, even though no one believes him</li>
<li>Along the way they stop at Omega Station (also imported from Mass Effect,
because why not) for light hijinks and to pick up 7 of 9</li>
<li>They don’t catch the spies in time and the Romulans damage the project near
to destruction</li>
<li>7 of 9 finds that she can at great risk reactivate much of the old Borg Tech,
gets repairs kicked off (just in time)</li>
<li>7 of 9 is <strong>so</strong> tempted to become the cube’s new Queen, but manages to get
new safeties installed with a bit of Federation flavor</li>
<li>Lost for where the trail leads next the Admiral and odd crew stumble into
a scientist on the project who thinks she has a good idea where the trail
goes next</li>
<li>That scientist (Soji) tells the Admiral that she had leaked details of her
home colony to the Tal Shiar agents, thinking them friends, but is cagey
about it</li>
<li>They go there and are greeted by the kindest Soong in the history of Soongs
and a whole colony of androids</li>
<li>The Tal Shiar threaten to blow up the place “because the Reapers will
return soon”</li>
</ul>
<p>In that order, even as just bullet points, I feel like that’s a pretty good plot.
Certainly better than what Season 1 of <em>Star Trek: Picard</em> seemed to <em>show</em> us,
right?</p>
<p>I think that leaves clear villains the whole way through with clear motivations.
I think that it leaves lots of building blocks for wilder things to come &quot;next
season&quot;. Again, I don't think I actually deviated from the story actually in
the show (near as we can figure in some places where things are far too much
subtext rather than text because the show generally abhors &quot;text&quot;).</p>
<h1>Missing &quot;Bones&quot; in Season 1 of <em>Star Trek: Picard</em></h1>
<p>I think that there's only a couple things that I would have loved to <em>see</em> or at
least have <em>told</em> in that season, rather than leave things to supposition.</p>
<ul>
<li>The Tal Shiar agents continue to evil monologue about the Reapers as confession
before a Federation war crimes tribunal</li>
</ul>
<p>I like to highlight the Federation ideals here. Don't murder the villains, let
them face justice and account for their actions.</p>
<ul>
<li>Don’t show the Reapers to any main character, but only tease the audience they
are real</li>
</ul>
<p>We don't know anything about this species other than a brief glimpse through what
might have been a portal. There's no text here, just wild special effects.</p>
<p>Beta canon suggests it <em>could</em> be any number of things with a common fan favorite
being the TNG-introduced Iconians of the Iconian Empire using a classic Iconian
Gateway, which yes all do predate Mass Effect by some time.</p>
<p>Despite mocking this element of the show as just ripped from Mass Effect, I
appreciated. I like my Mass Effect most when it is a lot like Star Trek, and
I did like a lot of things about when Season 1 felt the most like &quot;Picard Effect&quot;.</p>
<p>I love the idea that at all times in Starfleet history there are barely known
existential threats that are just out there, encountered once, if at all, and
&quot;never followed up on&quot;. Part of the fun of something like the Beta canon is
all the fan hype about &quot;This time it is the Iconians for sure!&quot; but Alpha canon
has never mentioned them again outside of a throwaway gag in Voyager and a
throwaway mention in Discovery (centuries after TNG).</p>
<p>It's great to have these bits of unexplained whatever. &quot;Give us more tell&quot; also
doesn't mean &quot;tell us everything&quot;, it's still a pendulum. Briefly show and
leave unexplained works well when <em>intentional</em>.</p>
<ul>
<li>Redeem Soongs and invite back to Federation, finally</li>
</ul>
<p><em>Star Trek: Picard</em> seems to get so close to doing this. I like the idea of
a completed redemption arc for the Soong family over way too many generations.
The Soongs are exiled from the Federation for getting too deep into
Federation-outlawed genetics experiments, while in exile realize that genetics
experiments are tough without a big enough population to experiment upon, and
pivoted to never technically outlawed by the Federation research into robotics
and sentient machines. (Obviously the Federation invested quite a bit into
these things still. The Exocomps as the easiest next thing to point to. It
was certainly not outlawed in the Federation to science these things.) It is
plausible that being in exile gave them a somewhat leg up and some of their
experiments even in that time might not have met Federation ethics standards.
But TNG and then <em>Star Trek: Picard</em> make it clear that once the research was
complete, the Federation had much fewer qualms about taking advantage of the
final products. (After all, what <em>is</em> the measure of a man?) (At least until
that Romulan spy sabotage thing, whoops.)</p>
<p>The right thing to do would be to welcome the Soongs back into the Federation
after their androids (and &quot;synthetics&quot; derived from same) had done so much for
the Federation for so many years. It's cool and all to see &quot;yet another&quot; sad
goodbye to Data himself, but welcoming the Soongs back into the Federation I
think would be a much more interesting, weird, but satisfying ending that the
show could have uniquely given us. It's a finale to a TNG arc (that ENT
expanded) a lot of people wouldn't see coming but I think would grant a strange
amount more of closure to Data as a character than any &quot;chess ending&quot; or &quot;poker
hand&quot;.</p>
<h1>The Borg Coöperative Bones of Season 2 of <em>Star Trek: Picard</em></h1>
<p>I think the bones are there for something really interesting in S1 setting up
a teed shot for the Borg Coöperative. S2 mostly seemed not to know how to use them.</p>
<p>I think some of these were good bones in S2:</p>
<ul>
<li>7 of 9 is given a glimpse of being “Queen” in a fascist timeline or mirror
universe and further reviled by it</li>
</ul>
<p>The Borg Coöperative should not have a Queen. It should be more of a democracy.
That idea that the Collective's culture is so strong that it really wants <em>a</em>
Queen, and that in that position Seven of Nine realizes she's incredibly
tempted and has such a hard time resisting. (It is futile, as they say.) Using
an alternate timeline or the mirror universe to push that message on paper is
a good way to build up that kind of resistance, to help build the sort of
person that could take Collective technology and democratize it. The sort of
person that could be the first, term-limited &quot;President&quot; of a Coöperative.
The kind of person that could stand up and say &quot;No more Queens!&quot; and mean it.</p>
<p>(Agnes doesn't really make sense at all in S2.)</p>
<ul>
<li>The Borg Coöperative somehow uses a time loop to bootstrap an interestingly
sized fleet; Borg and time travel are peanut butter and jelly</li>
<li>Only one ex-Admiral can convince The Coöperative to join the Federation as allies</li>
</ul>
<p>There is a fun symmetry to be had in using <em>Star Trek: First Contact</em> style
shenanigans to help strengthen the Coöperative (versus reduce the Collective).
(But do it on purpose, with motivation! You don't spend nine-tenths of First
Contact's runtime wondering if the Borg are friend or foe and accidentally
helping them instead of fighting them.)</p>
<p>On paper, the idea of Jean-Luc Picard being invited to be the ambassador to
the Borg Coöperative for the Federation also has some delightful symmetry.
PTSD included. So far as I recall that's not even an idea that I think
much of Beta canon considered, but it's an interesting one (that S2
squandered).</p>
<p>There too, motivation would add <em>good drama</em>. PTSD gives Picard a lot of
reasons not to trust the Coöperative, and have a tough time imagining
people that might volunteer for the sort of thing thrust upon him as
Locutus. <em>But</em> if the audience has a good idea that the Coöperative is
who they say they are, <em>up front</em>, that's good dramatic tension, not just
&quot;he's probably right, no one should trust the Borg&quot; which S2 suggests
right until the last minute &quot;twist&quot;. It places Picard in the role not
as the Ambassador friendly to both sides from the start, but the
Ambassador doing due diligence to grow a proper trust (and maybe never
quite getting there but still trying to at least do the right thing
for the Federation).</p>
<p>But beyond the good bones above, I still wish that the reclamation Cube
had been more involved somehow in the S2 Coöperative. It was setup so
interestingly for that. I wish Seven of Nine was more properly involved
in <em>electing</em> leadership. That also seemed interestingly setup but then
not paid out. I wish the Coöperative was less reliant on alternate
timeline resources and some sort of continuity drawn back to the Voyager
episode. Not because I'm a stickler to any of the Beta canon origin
stories, but because a &quot;worker's coöperative&quot; should come from the same
place it is in rebellion against and there <em>is</em> such juicy fun in the
idea that Voyager, of all ships, with no other help, accidentally seeded
an entire faction of &quot;good Borg&quot; in the home turf of the Borg while just
trying to get home.</p>
<p>It's been a long road, getting from there to here. I've continuously
explaining to people why I think S1 had some good ideas despite poor
execution and that S2 was worse because the Borg Coöperative is a
<em>great</em> idea and it was so poorly executed most people have no idea
at all, whatsoever, what &quot;Agnes' Borg&quot; were even supposed to be.
(And they still might not even be the Coöperative. That's still
more subtext and showrunner tweets after the fact than anything
substantive, especially since they were too busy to lend a hand
at all in Season 3.)</p>
]]>
      </content:encoded>
      <category>tv</category>
      <category>star-trek</category>
      <pubDate>Sat, 29 Apr 2023 00:00:00 GMT</pubDate>
    </item>
    <item>
      <title>On Bluey, new PvP in Sea of Thieves, and Why It is Sometimes Fine to Suck at Warcraft</title>
      <link>https://blog.worldmaker.net/2023/01/08/bluey/</link>
      <guid isPermaLink="false">https://blog.worldmaker.net/2023/01/08/bluey/</guid>
      <content:encoded>
        <![CDATA[<p>In November and December I had a maelstrom of different things smash together that all sort of reminded me
of strange parts of my education, all the while I was fighting some illness that was mildly Flu-like. First
chronologically, while I was in the middle of trying to win NaNoWriMo, <em>Sea of Thieves</em> released their newest
PvP mode for which I have a lot of criticism to discuss. Then I was hit by that Flu-like thing just after
Thanksgiving and used that as excuse to binge watch all of <em>Bluey</em> that Disney+ had available. Whatever that
was had responded to anti-histamines so I'm mostly sure it was a perfect storm of different seasonal
allergies hitting me all at once and a couple of them in ways I haven't felt in years, so I don't think it
was contagious but I was also trying for caution. Between the fevers, the migraines, and the binge watch I
came out of watching <em>Bluey</em> feeling somewhat like I was going to write a PhD dissertation on it or, deep
enough in the fever, like I already <em>had</em> written a PhD dissertation on it. (Unfortunately, I don't know any
college that awards Doctorates for virtual PhDs written about children's TV shows while running a fever, but
I still kind of feel like I earned one and someone should award that to me.) Then deep in the middle of <em>Bluey</em>
PhD brain I was nerdsniped by a Folding Ideas video that seemed almost pointedly wrong given the PhD
dissertation I &quot;had written&quot;.</p>
<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
channels, but seemed something interesting to coalesce into something of a better narrative form to blog about.
Beyond the through line of all of it happening in short succession chronologically, I think there's a lot
interesting cross-threads and maybe a useful overall narrative to all these various sorts of games criticism.</p>
<h1>A Quick Recap of My Odd Academic Background</h1>
<p>To give some context to everything, allow me to briefly recap my academic background a tiny bit. In undergrad
during my cooperative education internship with Microsoft I attended my first Penny Arcade Expo (&quot;down the street&quot;
that year from where I was living in Redmond/Bellevue). While attending PAX I was reminded that in High School
I'd thought I would like to make videogames, that coalesced with some of the games criticism I'd been blogging
at the time, and I caught something of an entrepreneurial bug there. That lead me to discovering a card game
that I loved, with great artwork, and from there working to license it to build a Xbox-playable version. I
decided that was going to be a big grad school project for me. My program was a targeted Master's Program and
I had gone in with the assumption of finishing grad school (despite it being a degree that doesn't quite matter
so much in the software industry as whole, it mattered to me at the time).</p>
<p>I treated the entrepreneurship as its own grad school challenge, including academically. I used the small
business as an excuse to pick up a lot of game design textbooks, especially those covering MMO design for several
reasons. The card game I was working on wasn't an MMO, of course, but was multiplayer. I had some ghost of an
idea that given Adventure games were still mostly dormant outside of a few small developers that MMOs were an
evergreen genre that if I could bootstrap into them could be very interesting. Here I had games like
<em>Puzzle Pirates</em> as a particular influence at that time on ideas you could accomplish in a &quot;low fi, bootstrapped&quot;
MMO. I've still never actually tried to bootstrap a low fi MMO, but I've still got lots of notes on various ideas
from different years.</p>
<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
videogame and game design in some brief moment in roughly 2007-2009. I capstoned that grad school work with a
brief chance to be a panel member on what I felt at the time to be a great PAX session in 2009. I failed to
predict the sort of economy that would greet me after I completed grad school in 2008, and the card game I was
working on at the time was literally stomped on by Godzilla (which is funny now with distance, but of course was
not at the time when my expected livelihood sort of depended on it).</p>
<p>That weird academic background I'm very proud of, but I've never made professional use of it. Perhaps it says
something that my fever dreams thought I should apply it back to academics and go for an Imaginary Doctorate
I wouldn't know how to use professionally either. I still don't know exactly where that came from.</p>
<h1>Bluey!</h1>
<p>Games are incredibly important to the (Australian originated) children's television show <em>Bluey</em>, almost every
episode is about games and even the intro to every episode is itself its own mini-game. I don't believe this is
a coincidence and I think this is underscored by the fact that the production company for the show decided to
call themselves Ludo Studio, with ludo being one of the Latin names for games/play, and the preferred one for
things 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
dissonance (the difference, especially when troubling, between what the narrative, the story/theme, of a game
is trying to say and what it's game mechanics are built to impart).</p>
<p>Almost everything in <em>Bluey</em> is a game, including the intro, as I mentioned, follows rules, explores rules, and
deals with concepts like fairness in play, and fairness in rule design, and even complex things like
ludonarrative dissonance. It explores all those things, plus family and friendship and growing up and so much more,
all in very tight 9 minute episodes. It's equal parts delightful (for instance, every time someone wins a game,
&quot;Hooray!&quot;), and emotional (some things get sad for the talking dog families of Bluey's world). I don't expect
anyone needs a recommendation from a childless adult who binge watched the show in massive single sittings as
comfort food during fever-like illnesses, but I loved it and strongly recommend it, and I would like to think
that it it is the kind of show that can both hold young kids' attention, and maybe teach them something.</p>
<p>It deals with some of the problems of game design, and offers hints of ways to solve them. It tries to instill
ideas of fairness, of course, as many good children's shows do, but it also gets into some of the deeper things
beyond simple ideas of fairness in game play to why you need rules in the first place and what their goals are,
how to have fun playing with people that have different ideas of fun or games than you, and how to troubleshoot
cases where people stop having fun. Overall these are great life lessons that apply equally to games and things
that aren't games. It is said that some of the games children play are deeply to &quot;rehearse&quot; real life. The
difference between real life and game play aren't binary, they are things that bleed over and between each other.
That's a strong theme of <em>Bluey</em> throughout and some of the most fun episodes leave you wondering how much
&quot;magic&quot; was real life. You get the sense over three seasons that Bandit, especially, Bluey's dad, will do
<em>anything</em> for a game and is possibly the best, most spirited games player so long as his kids are having fun,
but sometimes Bandit leaves you with questions of how much he's playing and how much is real (and that's part
of the fun of the show). That's the power of a good game player sometimes. On the other side, many of the games
throughout the seasons have deep real world repercussions and many things are &quot;for real life&quot; (including, among
other things, the friends you make along the way and the family you keep).</p>
<p>While watching, I continuously had that weird feeling that so many episodes of <em>Bluey</em> showcased complex things
from my weird grad school education but in relatable, easy to understand ways. That was at least part of where
the idea came from that I could spend hours lecturing on individual 9 minute episodes and how they relate to a
textbook's worth of ideas on how you design games, on how you run them, on how you should act as a good player
of games. Somewhere along the way &quot;lecturing on it&quot; turned into &quot;writing a blog post&quot; (such as this on it) and
then became it's own little Imaginary PhD dissertation that I dreamed I maybe actually wrote. There would be a
lot worse things in life than to hold a Doctorate in Ludology As Explored by the Children's Television Show
<em>Bluey</em>. I think fever dream me earned it, and I probably don't have enough moonlighting time to try to do it
in real life, but if there's a college interested in handing out that diploma, I suppose they could email me.
(And as <em>Bluey</em> reminds us continually sometimes the things you earn in a game still count for real life. I'll
treasure the one I scrawled in metaphorical crayon for myself.)</p>
<h1><em>Sea of Thieves</em> Has a New PvP Mode, Y'all</h1>
<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
it is a great replacement for the lost Arena mode (and many people just call it &quot;New Arena&quot;, which I will have
difficulty myself not just referring to it as that), I like that it uses Adventure servers in interesting ways,
and 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
better way than the &quot;separate but not quite equal&quot; menu mode that Arena was. Yet in practice, I'm highly critical
of it, how it is balanced, and especially how it is rewarded. It's very easy for this criticism to sound deeply
critical in the negative connotation sense that I don't like it and/or maybe even just hate it, so I suppose
that's why my brain spent a lot of time metaphorically blowing the cobwebs out of some old parts of my grad
school education and looking to find ways to constructively criticize it (in the proper denotation of criticism
as not specifically negative).</p>
<p>My harshest take has been that the new system is &quot;Emissary 2: Emissary for Masochists&quot;. The new system introduced
two new Emissary factions that were clones of previous trading company/emissary factions but &quot;PvP Only now&quot;. It
took some existing concepts such as the Emissary flags and their relationships with factions and built new
slightly different versions but &quot;PvP Mostly now&quot;. There's some good reasons to do that, of course, in that it in
theory started everyone off on an equal playing field and they felt that PvP was undervalued so having &quot;PvP Only&quot;
content was a boost that they felt the game needed after lots of PvE fine tuning over the last few years (and the
shutdown of the Old Arena).</p>
<p>A useful lens for this to me remains the Bartle types. These were types of players as self-described on early MMOs
and then compiled and studied by Richard Bartle among others. The Bartle types are sometimes considered an outdated
or flawed methodology for several reasons: they were self-described, which can add some bias to the results, and
they are sociological profiles that are quite (intentionally) broad. At its worst usage, the Bartle types are &quot;MBTI
or horoscopes for MMO players&quot;. At its best, however, there's still some usefulness in the way that Bartle types
can describe a &quot;four quadrant&quot; game that hits many key interests of multiplayer game players. Despite being
self-described, early studies of MMO players showed an equal split among players of which of the four types they
personally felt most dominant, and that's still a useful rule of thumb property for first order approximations.</p>
<p>I think my greatest criticism (as compared to my harshest take) about the new PvP system is not just that it is a
&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
balanced and based on that self-described demographics &quot;rule of thumb&quot; it's the kind of thing that applies to
likely less than twenty five percent of possible players. We already know that was a problem with the Old Arena
and why it shut down, in that it was used by fewer than 25% of the player base. The new system doesn't seem to
repeat some of the same mistakes that made maintaining the Old Arena expensive by sharing entire instances with
Adventure and not needing different or dedicated servers just to run it, plus it isn't supposed to diverge from
the rule set in play in Adventure in any significant way and is designed to interact with existing Adventure
rules rather than replace them. It maybe isn't the harshest possible take on the new system that in the long
run it may again be less than 25% of players using the system because it maybe <em>is</em> better designed to survive
that. I still find it is a useful to lens to discuss and criticize the system in this manner though, as
particularly my own interest in the system comes from a perspective primarily based in the other &quot;three quadrants&quot;.</p>
<p>For brief recap: the Bartle types are generally referred to as Achievers, Explorers, Socializers, and Killers. A
relevant subset of Killers that isn't often considered a Bartle type of its own but has made its way into a lot of
MMO discussions in general, the vernacular at large, and should rarely be ignored is Griefer. Bartle described the
two axes connecting these four quadrants as Players versus World and Acting versus Interacting. In brief rough
overview: Achievers primarily seek to act on the world (scoring points, winning things), Explorers primarily seek
to interact with the world (finding hidden spots, solving puzzles), Socializers look primarily to interact with
other players (hanging out, winning together), and Killers seek to act upon other players (killing them, of course,
but more generally just in getting some emotion back out of them). Griefers are a subset of Killers whose primary fun
is getting specifically the maximum amount of <em>negative</em> emotions out of other players. (<em>Bluey</em> has great episodes
on nearly every Bartle type except Killers and/or Griefers. If the show needs suggestions on smart topics to bring up
with complex needs and real world repercussions, I offer the suggestion that there is rich ground there to cover,
though I also expect writing much on the subject for a children's show seems to me to be rather tough.)</p>
<p>It's very clear that any PvP system is first and foremost primarily focused on the Killers quadrant. That should be
obvious from the Bartle type name. I think an issue with the new system, because it is entirely opt in (for good
reasons, of course, I'm not complaining that it is opt in) it winds up being able to capture the attention of only
a 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
forums and Discords that the new PvP system would also pull a lot of the Griefers out of &quot;raw&quot; Adventure and push
them into PvP cycles instead. That personal goal of maximizing other player's misery for the Griefers generally
means that they won't opt in for long (or much at all), because random ships doing normal random stuff in Adventure
without marking themselves as &quot;interested in PvP&quot; is always going to be the more &quot;lucrative&quot; target for them and
their needs for their idea of fun than other Killers that opted-in for sport and are prepared to lose and are
prepared to minimize their losses.</p>
<p>A somewhat related disinterest in the new system I've seen from some Killers I've talked to with the new PvP system
is that the ranking system for matchmaking doesn't have a visible &quot;numbers go big&quot; metric for them to watch and
brag about like many other common Killer-focused games such as the <em>Call of Duty</em> franchises, <em>Rocket League</em>,
<em>Fortnite</em>, etc. Many of those make matchmaking stats much more obvious, notable and sometimes directly and
obnoxiously in their face. These are Killers mostly on the Killer-Achiever border that want that exciting
achievement to brag about that directly reflects their Killer side. It somewhat makes sense that <em>Sea of Thieves</em>
isn't making ranking numbers a part of the UI: they want to keep it somewhat secret sauce, because it needs to be a
crew ranking (team ranking) it by necessity has to be an aggregate across multiple players (which makes it harder
to brag about personal scores), and in general <em>Sea of Thieves</em> tries to feel like an even playground/sandbox so
such numbers are also somewhat counter to that spirit of play.</p>
<p>I know the developers of Sea of Thieves tried to mitigate this somewhat with the &quot;Defensive&quot; option to play the
new systems, where it is most and worst like &quot;Emissary 2: Glowing Figurehead Boogaloo&quot;. This mode suggests that if
you 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
will in theory gain other rewards faster (when you successfully defend your ship and hoard of loot). This was the
original promise of the Emissary system itself: put a target on your back, gain faction rewards faster. To be fair,
the game even lets you stack both Emissary 1.0 and Emissary 2.0 for higher risk/reward.</p>
<p>The original Emissary was balanced so that the target on your back is visible mostly only up to the edge of the
current horizon, unless you were representing the &quot;sometimes PvP&quot; Reapers faction in which case your target is
always on your back but you get potentially greater visibility of other Emissaries, eventually, if you do well.</p>
<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
target up on your back for all to see no matter what and remains always visible on the map. It adds the additional
risk of &quot;invaders&quot; that can spring out of the water at any time. (&quot;Invading&quot; is of course the other option from
&quot;defending&quot;.) Additionally, the new Emissary cuts out most of the PvE-based reward gains unless you have specifically
engaged in PvP at some point and won. This greatly increases the risk/reward, and so much so, that I have yet to
find anyone that thinks Defensive mode is worth the time investment. I knew enough players that were afraid enough
to raise the old Emissary flags at all, even with the tighter horizon constraints, and the new one has far less
appeal than even that. Killers, in my experience, want the easy/fast/on demand instant action of the &quot;Invading&quot;
mode, and while the Achievers are given plenty of commendations to try to achieve in the new mode and Explorers
and Socializers are teased with role playing-friendly social zones to eventually access/explore if they force
themselves to do enough of the new PvP mode, there doesn't seem to be quite enough PvE reward for all of the
extremely high risk. If you are going to put the time to put in all the PvE work to build up a good loot haul,
you have very few reasons to do it with a target painted on your back at all times given the time investment.
The current short term rewards don't seem to align well versus the time investment, in my experience and
anecdotally from other players' experience that I've heard.</p>
<p>Anecdotally, from doing a lot of grinding of &quot;Invading&quot; very few seem like players actively defending a loot
haul and the vast majority seem like &quot;fellow invaders&quot;. Some of the more hopeful on the forums think this is
a temporary case, but I think from the Bartle lens above and examining the current risks versus rewards, I
think it is unlikely to ever shift, especially with the December mechanics rebalance. (The December mechanics
rebalance added more ways to speed up the &quot;Invading&quot; cycle, further making it the key focus of the new system
and further leaving me questioning who the &quot;Defending&quot; mode is for given its risk/reward balance.)</p>
<p>My &quot;home&quot; perspective is from somewhere shallow into the Explorers quadrant in that on a given night I might
lean into the Achievers side and feel the completionist bug, or I might lean into the Socializers side and
just want to hang out with people. I can't say I've ever felt much of a Killer itch natively. (There is
sometimes a Socializer peer pressure for a bit of blood lust and I'm not immune to that, but my primary fun
in those cases still feels a lot more for me like &quot;socializing&quot; than &quot;killing&quot;.) I feel frustrated that cool
new social areas were opened to explore with seemingly impossibly high grind requirements for non-Killers.
On the one hand this certainly gives me a reason to play these new systems with a Socializer or Explorer hat
on, sure. On the other hand this makes me somewhat miserable and upset with the experience and while there are
Killer-Socializers that might appreciate the new spaces, a lot of the Killer quadrant won't appreciate that
sort of new content as much as other quadrants. The Bartle types as a lens also generally suggest that
Killer-Explorers may be among the least likely minorities in multiplayer games because that pairing crosses
not just one but two axes at once. (Again, Bartle types are just a first order approximation of a player base
and it's possible game metrics may even suggest it is a larger minority than that concept of abstract axes of
interest imply. Approximations remain useful for outside analysis though because I mostly don't have access
to any <em>Sea of Thieves</em> statistics but my own.)</p>
<p>Again, it is hard not to let my personal experiences sound too much like I hate the new systems on principle.
I appreciate that true Killers have needed systems like this since the shutdown of the Old Arena and not
every 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
likely will again, that's the nature of my relationship with the game at this point. I just also am the sort
of meta-gamer that I will do a semi-academic breakdown of the process and over-evaluate it, and then blog
about it, because that is fun in its own ways to me.</p>
<p>As an Explorer, I've explored how repeatably badly I can intentionally <em>suck</em> at the new PvP to grind my way
towards its social rewards with the least emotional investment on my part (because that just gives me anxiety
and isn't fun for me) and preferably the least amount of time investment. This is the meta-game I've been
playing with the new system. I think I have a system at this point, an algorithm of sorts that is just
<code>do x, y; wait until z; do a, b; wait until c; repeat</code>. The completionist in me is haunted that this system
doesn't quite earn me a bunch of statistics for some additional commendations and I'll have to do a lot of
this grind <em>again</em> with crews of enough Killer hats anyway if I eventually care for completionism, but I
never completed the Old Arena either (and its achievements still haunt my Xbox, &quot;Legacy&quot; mark or not; I was
90%+ in one but couldn't care enough before the Arena shut down and now that's just going to be forever stuck
at the top of my &quot;nearly completed&quot; list in Xbox dashboards and a constant reminder in that Xbox sidebar that
shows up when you pause a movie and the Xbox is close to sleeping, sigh).</p>
<p>It's kind of fun for me at this point, boring rote repetition aside, seeing how fast and how often I can lose
matches. Though I've still got a large number of losses left before I get pity access to the new social areas.
I've explored many of the ways to lose matches and narrowed down the ones that give any reward and then
narrowed down a few that give the most possible loss reward for the amount of time investment. (Again, I have
built a system for losing. I am maximizing my losses.) I am deeply amused at my growing count of accidental
wins. (My current system does not involve firing a single cannon shot at an opposing vessel. Wins truly are
accidents entirely out of my control at that point.)</p>
<p>I've discussed my system with fellow completionists and some of them have been appalled. They don't want to
ever grind just for losses. They can't imagine what that would do their rankings. They hate what that might
do to standings that they can't even see (because as mentioned above, that is not something the game is
interested in visualizing). But we can meta-game that a bit, too: So far the development team have stated
that a crew's ranking is a pretty straightforward average of each person's win/loss ratios. Someone with a
lot of losses brings the crew average down and makes battles on average easier (when easier opponents are
available). I don't care about my PvP metrics and I'm more than happy to be the good luck albatross of
easier battles. I'm not saying losing a lot is a great strategy in the long run, but there are certainly
tactical advantages to it. It's okay to suck sometimes, especially if that's more fun for you and
you aren't a jerk about it. (<em>Bluey</em> has an episode or three about that.)</p>
<h1>Why it is Sometimes Fine to Suck at Warcraft</h1>
<p>Folding Ideas somewhat recently dropped an hour and a half dive into the Raid-focused and end game
Metrics-focused culture in the MMO <em>World of Warcraft</em> called
<a href="https://www.youtube.com/watch?v=BKP1I7IocYU">&quot;Why it is Rude to Suck at Warcraft&quot;</a>. This dropped late
in my <em>Bluey</em> binge and its Imaginary PhD dissertation and with the giant swirl of <em>Sea of Thieves</em>
meta-game thoughts above. I paused somewhere around nine minutes and forty seconds into the hour and a
half pseudo-documentary and said to a Discord that was discussing it something to the effect that it was
a) poorly researched, and b) the entire hour and a half could probably be summarized by a single tight
9 minute <em>Bluey</em> episode.</p>
<p>I'm not that wide or deep in my &quot;pure&quot; academic background. Most documentaries I gloss over when
references and citations fly by. I focused on hard, practical engineering work for the most part, including
that instead of doing a Master's Thesis I opted for a Master's Project as the more practical option. (I
built a simple &quot;game engine&quot; for analyzing swarm algorithm mechanics, a neighboring sub-field to machine
learning, in a light JS/Self-inspired custom language REPL designed specifically for very easy &quot;follower&quot;
mechanics by default. Maybe not that practical outside of specifics to my advisors at the time, but more
practical than the average Thesis.) But it turns out that there's one domain where you can directly call
out my strange academic background: the one I already mentioned, games criticism and academia circa 2009.</p>
<p>That roughly 9 minute stop in the Folding Ideas video felt like a direct call out to me specifically. I
jumped out of my chair at the very first citation and went back and paused it to reread it and make sure
that it was as bad as I had assumed. The video was quoting a 2006 &quot;text book&quot; that was among that deep dive
I did in grad school (and I believe is in one of the many boxes of my personal library cluttering my den
for the last couple of years, because I keep procrastinating dealing with them). But it wasn't quoting it
directly, it was quoting it from inside of a not directly related article. It was citing a quote without
having directly read the book it was quoting, getting the quote itself only second hand. That's a huge
academic faux pas in general, but sometimes necessary when dealing with long out of print stuff. 2006
doesn't seem to qualify to me as &quot;long out of print&quot; (there are hardcovers for sale for as little as $6
used on Amazon).</p>
<p>The further you get into the video the further it becomes clear that their reading overall stopped well
short of many primary and secondary sources. Though to discuss why I think we need to first discuss the
conclusion of the video and so this needs a massive <strong>spoiler warning</strong> for anyone looking to complete
the Folding Ideas video. I include a spoiler warning here because I've appreciated many Folding Ideas
videos before this one and shade aside to this one I wouldn't have spent 45 minutes watching a half-hour
video at 2X speed, several Discord and Mastodon threads, and this much time writing up a blog post on
something I didn't otherwise respect. I just want Folding Ideas to do better, I'm not interested in
sending pitchforks in their direction. There is some good content meat in the sandwich of bad academics
that could have been better researched. Go watch that video if you are so inclined and come back to
this post unspoiled.</p>
<p>The conclusion of the video is basically (and baldly) that videogames are soylent: they are made of
people. This should not be a shocking conclusion to anyone in the history of games. This wasn't a
shocking conclusion in 2009 or 2006. This is three-fourths of what <em>Bluey</em> is about in almost any
given episode. It takes people to play games. People bring their baggage along with them into the
games that they play.</p>
<p>The weakest part of the Folding Ideas conclusion to me though is that it ends without any suggestions
on what to do about anything discussed in the video. It ends with roughly a noncommittal shrug. This
is disappointing on a number of levels. The first one is because it took my immediate gut reaction
that I could substitute a random <em>Bluey</em> episode for the Folding Ideas (though it may take me longer
than an hour and half to explain why, as you can see here in this very blog post) and one ups it:
a random Bluey episode (or a half hour of them) would likely be a better use of time because they
offer better starting grounds of solutions to the problem statement. <em>Bluey</em> gets into lots of
suggestions about how you engage with players with different ideas of fun and how do deal with
inter-personal conflicts that arise in such situations. Certainly it delivers these suggestions in
school child terms, and a lot of <em>World of Warcraft</em> players would look immediately look down on any
suggestion that they should watch more of a children's show, but that's still a good foundation for
deeper discussions to start from than just shrugging at the question of &quot;What do we do about it?&quot;
It speaks directly to that conclusion the video thinks it makes: if games are people, the rules we
build to &quot;play nice&quot; and try to get children to learn from an early age, apply at <em>every</em> age. To
some extent it really doesn't matter if you are playing &quot;Keepy Uppy&quot; or <em>World of Warcraft</em> if you
can't play nice maybe you shouldn't play at all. Those are eternal lessons to learn about human society,
which <em>why</em> we work so hard to teach them to children.</p>
<p>As nearly a tangent here from &quot;Imaginary PhD dissertation&quot; brain: one of things I found so powerful
about <em>Bluey</em> and why it resonated so much was exactly this. The show starts from a lens of games,
and nearly everything in the show is about games, including the intro is a game, but the life lessons
are all so very <em>for real life</em>. It is said that games are a key component to how kids develop life
skills by starting in relatively safer spaces, but something that not enough people remember into
adulthood is that while games are built to be &quot;safer spaces&quot;, they are &quot;safe spaces&quot; only <em>somewhat</em>
free from real life complications, and school of hard knocks real world life lessons. Games are one
incredible part of how we train each other to better live in a society together, and the lessons we
learn both come from and extend back out to real life. (<em>Bluey</em> has many of its share of those same
sorts of games that children play to learn society itself: &quot;Doctor&quot;, &quot;Shopkeeper&quot;, &quot;Parents&quot;, and so
many more. Again, with <em>Bluey</em>'s studio calling itself Ludo Studio I cannot imagine much of this is
an accident, and I don't know it is my place to tell them &quot;well done&quot;, but I can suggest that at
least one fever-addled, childless adult was so impressed they imagined they wrote a PhD dissertation
on it.)</p>
<p>It's not entirely a tangent, though, because it gets back into the narrative of why it seems like
such a shame that Folding Ideas stopped so short in their own research. I know a lot of this about
why <em>Bluey</em> seems like such an incredibly well done TV show to me precisely because of that weird
education I gave myself. The Folding Ideas video gave two reasons why they thought it best to stop
short. The first I think comes directly from that confusion between &quot;safer spaces&quot; and &quot;safe spaces&quot;
and a second-hand criticism that the early literature focused possibly too much on &quot;safer spaces&quot;.
A lot of the literature did explore the concept of &quot;ludic spaces&quot; as &quot;safer spaces&quot; and what that
meant about games, but most of it was <em>specifically</em> coming from that &quot;we think of games as
childish or child-like&quot; and directly defining how &quot;ludic spaces&quot; may seem &quot;safer&quot; at first glance
from a raw risk/reward standpoint than &quot;real life&quot;, but absolutely exploring all the ways that
&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
world consequences nor real world harm.</p>
<p>The very cover shown as Folding Ideas dismisses almost all of older research in this way is a book
of first and second hand accounts of people's lives in <em>Second Life</em> and deeply proving that any
attempt at &quot;second life&quot; is just &quot;real life&quot;. It gets into real life marriages that happened inside
of and because of that game. It gets into people who tried to establish very real businesses in the
game and in some cases for some brief moment made it work. It gets into real life crimes that
occurred in the game, some involving <em>Second Life</em>'s powerful scripting tools, and others involving
old fashioned terribly human societal problems like trafficking. A book mentioned elsewhere in the
video third hand but clearly not directly read got deep into <em>EverQuest</em> life and the societies it
formed and again very real things like marriages and divorces. All of these and more were in
conversation at the time with the situation that occurred in <em>Sims Online</em>. <em>Sims Online</em> was a real
videogame that EA spent millions of dollars to develop but never saw a single copy on store shelves
because in the relatively few hours of its Beta existence it spawned an entire organized crime ring
and allegedly had accusations that even got the US FBI's attention. That was the game that spooked
EA so bad that they got out of MMOs for nearly half a decade (despite being an early and profligate
publisher of them prior to that), and their lone running (though long-running) MMO today is
<em>Star Wars: The Old Republic</em>. (A lot of history rewriters today claim that EA got out of MMOs
because they couldn't compete with early <em>World of Warcraft</em>, but that narrative forgets key parts
of history that we shouldn't forget. This isn't even a &quot;distant past&quot; problem, even just recently
a parent sued the videogame <em>Roblox</em> among other defendants for its part in facilitating dangerous
real world trafficking.)</p>
<p>The other reason that the Folding Ideas video gives for why they stopped their research too short
is &quot;<em>World of Warcraft</em> was the first game to…&quot; This is patently untrue for many reasons, including
starting from their own conclusions. <em>World of Warcraft</em> was never the first game to be played by
people. Compared to marriage, divorce, running a business, real world crimes like extortion and
trafficking, their concern of &quot;some people in WoW were mean to me because I wasn't using
endgame-class/raid-level analytics tools and tactics&quot; feels utterly <em>banal</em>. It feels almost as
childish as those same WoW players would likely dismiss <em>Bluey</em> for being too childish and
inapplicable to their circumstances, despite being almost the very sort of problem <em>Bluey</em> was built
to teach about. Even extending that &quot;first game&quot; through the video's various qualifications and
hedges: WoW wasn't the first MMO with deep scripting. <em>Second Life</em> as mentioned had user scripting
crimes even. Nor was it the first RPG game-like MMO with deep scripting. <em>EVE Online</em> has had more
than its fair share of mod tools (and Excel spreadsheets!), for longer than WoW. Reaching back
further to text MMOs everything that the video talks about happening with mods and analytics tools
happened in the history of text MMOs. Text MMOs used Telnet, an unencrypted, dead simple
communications protocol and there was no way for text MMOs to have any control of mods and analytics
tools (and many also had server-side scripting tools as well). The history of text MMOs is also
fascinating here because there was an &quot;other side&quot; of the hill that graphical MMOs still haven't
quite hit (though ones like <em>Second Life</em> try): when everyone has scripting tools at their disposal
at some point it stops being interesting endlessly data mining the existing content and there's a
deeper push to just build your own content. Many of the text MMOs with the most power in user
scripting were also the most free in terms of possible content and how people could play/engage that
content. There was little need for &quot;perfect tactics&quot; because there was always more content. Graphical
MMOs have a while to go before we see that to any degree like text worlds saw (though for another
instance Cryptic Studios certainly has spent a lot of time exploring it with their &quot;Foundry&quot; efforts
and tools in their various MMOs over the years as well).</p>
<p>Academics is certainly a constant process of learning new things and applying what you learned in
new ways until eventually you discover a better idea of things. But that also doesn't mean that you
can just throw out all of the academics from before you were born or your favorite game was born. The
Folding Ideas video's own citations imply several places where the team could have &quot;followed the links&quot;
to earlier works that would have <em>enriched</em> the video and stopped just slightly too short to help even
their own conclusion. Some of those books they skipped might have gone a long way to helping them come
up with suggestions to make the game's culture friendlier than the shrug they ended with. Different
players exist with different ideas of fun, those early books spent a lot of time trying to describe
that and coming up with very rough approximations such as the aforementioned Bartle Types, and trying
to find ways to make more games appeal to &quot;all four quadrants&quot;. That many of the games they described
are gone and long unplayable doesn't make them any less relevant today. In some ways it shows how much
more games need to mature that we so easily forget entire <em>cultures</em> of players, just as anthropologically
lost as any empire lost to ruin, and often barely covered in a handful of first hand accounts in academic
literature, such as the ones this video referenced but skipped. It's all the more reason not to ignore
the first hand accounts that we do have, to not just stop at third and fourth hand criticisms, but
read some of it for yourself.</p>
<p>I also have few illusions that I've done much better than Folding Ideas at beating a random <em>Bluey</em> episode
on these topics. This post is nearly seven thousand words and its own far cry from a tight 9 minutes of
television. (It's also still a hundred or so pages shy of that combined Imaginary PhD dissertation I
dreamed that I might have written.) I suppose the most useful takeaway is, go watch more <em>Bluey</em>. Also,
games are complicated and hard and full of real life and there will always be so much to say about them
and so much already said about them worth reading.</p>
<hr>
<hr class="footnotes-sep">
<section class="footnotes">
<ol class="footnotes-list">
<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>
</li>
</ol>
</section>
]]>
      </content:encoded>
      <category>games</category>
      <category>game-design</category>
      <pubDate>Sun, 08 Jan 2023 00:00:00 GMT</pubDate>
    </item>
    <item>
      <title>Film of the Moment: Slipstream (1989)</title>
      <link>https://blog.worldmaker.net/2022/11/25/film-moment-slipstream/</link>
      <guid isPermaLink="false">https://blog.worldmaker.net/2022/11/25/film-moment-slipstream/</guid>
      <content:encoded>
        <![CDATA[<p>I realize that I fell out of the habit of &quot;of the Moment&quot; posts as so
much of that sort of discussion moved to social media. I was reminded that it
might be fun to adapt this Mastodon thread that started as a
discussion on Discord into a &quot;proper&quot; blog post.</p>
<p>A Discord-posted meme of a tweet of how hard Mark Hamill looks in
<em>Slipstream,</em> a 1989 rarely seen film, reminded me that the movie had
been sitting in my &quot;To Watch&quot; pile for some time. I did not recall why
it was on the &quot;To Watch&quot; pile, it might have been because it was a
&quot;lost classic&quot;, it might have been as a &quot;so bad it is good 'classic'&quot;,
given the age of how long it had been on my &quot;To Watch&quot; pile it might
have been something random like possibly people talking about the
credits of Robbie Coltrane around the time of his passing. I may never
know, I did not take good notes.</p>
<p>The reminder that it was on my &quot;To Watch&quot; pile sparked the curiosity
to re-check on it on JustWatch, which told me that Tubi had it free
for streaming (with ads) and with encouragement from that Discord
channel I decided to watch it.</p>
<p>The was directed by Steven Lisberger best known for <em>Tron</em>, and was
his fifth and final film. IMDB says that the movie didn't get much of
any US release in 1989 because the production had bankrupted the
producer, most famously a producer of Star Wars. Interestingly, it
doesn't seem to have been budget overruns or the other usual reasons
for a production to bust: that producer went through a messy divorce
that allegedly included awarding the ex-wife the royalties from Star
Wars which was exactly where the <em>Slipstream</em> budget was coming from.
Oops.</p>
<p>I thought <em>Slipstream</em> wild and mostly fun. It was sometimes hilarious
in its schlocky, trope-filled pulp dialog. My overall impression was
that it was a bit of a &quot;lost classic&quot; in that way that if I'd stumbled
upon it on VHS in a hidden weird shelf at a Blockbuster at the right
age in the 90s I might have loved the movie growing up.</p>
<p>The closest comparison for several reasons seems to be <em>Waterworld</em>,
despite <em>Waterworld</em> having been produced after <em>Slipstream</em>.
<em>Slipstream</em> plays as something of an &quot;Airpunk&quot; <em>Waterworld</em> (an_
Airworld_? <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
reasons, but where <em>Waterworld</em> simply spent too much, <em>Slipstream</em>
seems cheaper and overall probably well budgeted if the personal
mistakes of the publisher hadn't interfered. <em>Waterworld</em> has a
seriousness to it that plays corny, whereas <em>Slipstream</em> seemed to me
to have an intentional playful corniness throughout (that trope-filled
pulp dialog, for instance), in ways that evoked to me a lot of old
pulp novels and radio/TV serials. (Much as <em>Star Wars</em> and <em>Indiana
Jones</em> mine those old tropes.)</p>
<p>The opening narration introduces us to the idea that after a
convergence of terrible climate change disasters, most of what remains
of humanity are only connected via a harsh air current known as the
slipstream. (No need to wonder what the title of the film refers to.)
The other obvious type of film to compare this to is a <em>Mad Max</em>-style
&quot;post-apocalyptic road trip adventure&quot;, with the interesting twist
that all the &quot;cars&quot; in <em>Slipstream</em> are gliders and small aircraft.
That gifts us a lot of great B-Roll and C-Roll footage of small planes
through valleys in Ireland and Turkey for the film's version
of a cave-filled, wind swept post-apocalypse. From the opening
overture the score goes all out to sell these plane trips as
incredibly important and maybe goes harder than it should, but I
greatly enjoyed that. Later in the movie those types of establishing
shots also introducing the movie's few &quot;drop&quot; tracks, amusingly
diegetic in those moments and some equally harder than they needed to
be tracks.</p>
<p>The movie is just full of some of the wildest (and most fun)
performances. The aforementioned Mark Hamill plays a blond-dyed
fascist cop and seems to have great fun hamming it up as the primary
antagonist of the film. (The above mentioned tweet was correct, his
hair dye and the films costuming choices go a lot harder than they
need to, like he was a cut extra from a <em>Matrix</em> sequel, but work well
in the context of the movie.) The primary protagonist of the film is
pre-<em>Twister</em> Bill Paxton having some of the most wild-eyed fun
possible, getting some of the worst, most hilarious one-liner dialog,
and chewing scenery along with it. Pre-<em>Jurassic Park</em> Ben Peck (as in
&quot;Clever Girl&quot; Muldoon) plays the heart out of a role that is too easy
for me to accidentally spoil. There's a blink and you will miss it
cameo from Ben Kingsley. Robbie Coltrane has the chance to steal a
couple of wild scenes. F. Murray Abramsom gets a strong scene. Just
about everyone in the movie seems to have the right idea of what sort
of movie they are in.</p>
<p>Strange caveats to mention:</p>
<ul>
<li>The violence isn't that remarkable for a PG-13 film, but the sexual
innuendo and near-nudity is kind of interesting. Some of the &quot;sex
scenes&quot; were quite weird, had some crazy dialog that would have made
parents I recall in the 90s super mad (but probably would have passed
my own parents' inspection at the right age, I think).</li>
<li>There's a third act plot choice that feels very &quot;nearly a fridging&quot;
that feels somewhat problematic by today's standards.</li>
<li>There's an &quot;enemies to lovers&quot; journey across the film full of some
questionable content, and what I thought was some questionably
problematic consent issues, especially in a late scene involving
handcuffs.</li>
</ul>
<p>Other than that it was a fun &quot;so (intentionally) bad it is good&quot; movie
with an interesting &quot;lost classic&quot; history and vibe to it.</p>
<hr>
<hr class="footnotes-sep">
<section class="footnotes">
<ol class="footnotes-list">
<li id="fn1" class="footnote-item"><p>Does that imply the eventual existence of an <em>Earthworld</em>
and a <em>Fireworld</em>? No one will expect it when the <em>Fireworld</em> movie
drops. <a href="https://blog.worldmaker.net/2022/11/25/film-moment-slipstream/#fnref1" class="footnote-backref">↩︎</a></p>
</li>
</ol>
</section>
]]>
      </content:encoded>
      <category>films</category>
      <pubDate>Fri, 25 Nov 2022 00:00:00 GMT</pubDate>
    </item>
    <item>
      <title>I Have Tried to Set a Better Example for Angular Components</title>
      <link>https://blog.worldmaker.net/2022/10/30/angular-components/</link>
      <guid isPermaLink="false">https://blog.worldmaker.net/2022/10/30/angular-components/</guid>
      <content:encoded>
        <![CDATA[<p><a href="https://blog.worldmaker.net/2021/06/26/angular/">Previously I blogged about many concerns that I have about the sorts
of examples that Angular sets (deep in its core libraries)</a> and how I
feel like it leads to a &quot;pit of failure&quot; when it comes to reliability
engineering and performance optimization. In my day job I feel backed
into a corner where I have to support Angular application development
and have had to become something of the performance expert and
performance &quot;officer&quot; against my early advice that we should have
picked anything but Angular. In the end of that complaint post, to
offer something constructive, I offered some bon mots about what you
might do if you were to rebuild Angular from nearly scratch (I called
that 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
the day though, I'm a pragmatic software engineer. It's always my job
to build my way out of problems, especially solving other people's
problems.</p>
<p>The short story is that I've built a growing collection of libraries
around what I've called the <a href="https://worldmaker.net/angular-pharkas/">Pharkas Component Framework</a>. It codifies a
lot my &quot;Observables-only&quot; best practices into what I hope is a &quot;pit of
success&quot; tool that's easy to slot into existing (&quot;brownfield&quot;) Angular applications
and migrate things a component at a time as you can. I think it is
incredibly useful out of the box and have been using it to improve
performance in production apps for months now. At the very least, I
hope these libraries serve as a good example to the Angular ecosystem,
whether or not it sees strong adoption outside of production apps that
I'm personally charged to &quot;grease the wheels of&quot;.</p>
<p>I thought I would narrate some of the longer story as well. I'm very
proud of <a href="https://worldmaker.net/angular-pharkas/">Pharkas</a> and what it accomplishes as an example to get around
what I think are  problems in Angular deep in the core libraries of
the framework, but I also feel like I need to provide at least as much
motivation and context as I can on why I built this to help answer why
anyone should trust Angular libraries written by someone that
unequivocally admits to hating working in Angular.</p>
<h1>I Picked a Losing Fight With Zone.js</h1>
<p><a href="https://blog.worldmaker.net/2021/06/26/angular/">When I last blogged about Angular</a> I was obviously already trying to
think of constructive ways to build my way out of the mess. I
mentioned 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
else to maybe put in the work, because at the time they mostly
revolved around replacing or somehow augmenting/extending Angular's
template language compiler. Angular likes to pretend that it doesn't
have a template language &quot;it just uses HTML&quot; and as you would imagine
this means that Angular's (massive) template compilers (there have
been several massive rewrites) themselves are somewhat &quot;secretive&quot; in
what of its internals are publicly documented. They aren't really
built for easy replacement or augmentation/enhancement. That lack of tools
support is the core to why &quot;Project Gawky&quot; was much more of a
pie-in-the-sky rewrite idea than a pragmatic solution to offer.</p>
<p>Soon after publicly documenting those thoughts on my blog, but while I
was still in the middle of thinking about trying to construct my way
out Angular, I got tossed into a massive performance fight where the
biggest production app I was working on would just &quot;stall out&quot; for
<strong>minutes</strong> of wall clock time. There was no noticeable network activity, no useful
&quot;progress&quot; indication, terrible responsiveness to user interactions
(&quot;slow&quot;/&quot;ignored&quot; clicks), and not even a &quot;please wait/processing&quot; beach
ball or spinning hour glass: it was just the absolute possible worst
user experience and it was making our production users angry.</p>
<p>There wasn't a clear indication of when the problem started, much less
if it was a performance regression specific to any recent code. (There
wasn't even a clear indication of a specific source/cause. The
reproduction was &quot;navigate the app randomly for long enough&quot;.) There
was some heavy calculation work in an observable pipeline that
recently was refactored just a tiny bit, so in terms of hypotheses,
and enough evidence that pipeline was shared by enough components on most pages that
was my best idea of a place to start. I started in the obvious places
of making sure that the pipeline wasn't over-subscribed, wasn't
leaking subscriptions without unsubscribes, and wasn't accidentally
over-observing to many input events from other pipelines.</p>
<p>I started with a lot of <code>tap</code>s and <code>console.log</code>ging debugging, and in the
middle of that was pointed to <a href="https://github.com/cartant/rxjs-spy">RxJS-Spy</a> which is a fantastic debugging
tool and I can't recommend enough. It provides a simple tag operator
where you give a pipeline a name, which is a no-op in production
builds but in debug builds gives you an entire dev console framework to
spy on specific pipelines by name or groups of pipelines by regex. It
offers the ability to choose between <code>console.log</code>ging and debugger
breakpoints. Again, it's just a great improvement on &quot;tap-style&quot;
debugging. Install it today. (I have nothing to do with <a href="https://github.com/cartant/rxjs-spy">RxJS-Spy</a>, I just
keep recommending it to projects now.)</p>
<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
observable pipelines didn't have obvious leaks and were observing
things at a pace that seemed reasonable, including the massive
possibly expensive calculations I was worried about in my hypothesis.
At this point I had easily disproved my hypothesis.</p>
<p>This is the part of debugging that gives everyone nightmares: all of
my team's code is working just as expected. Does that mean the
performance issue isn't in my team's code?</p>
<p>In just about any other framework I would have already have pulled out
&quot;flame graphs&quot; from the Browser's performance developer tools and been
trying to base my hypotheses on real, hard evidence, not just shooting
from the hip in the dark or trying to litter the entire code base with
<code>console.log</code>s in the hopes that I could guess at performance
bottlenecks. In Angular it is really hard to get useful data out of
flame graphs for one specific reason: <a href="https://www.npmjs.com/package/zone.js">Zone.js</a>.</p>
<p>Zone.js is a supposed &quot;prollyfill&quot; to implement a JS proposed feature
that ECMA Technical Committee 39 (TC-39) shot down years ago for being
dangerous, confusing, and not generally useful. So far as I'm aware,
Angular remains the only &quot;customer&quot; of Zone.js, and today is entirely embedded in the Angular repo. Angular uses Zone.js
<em>deeply</em> to power its change detection systems. Zone.js works by
monkey patching the entire JS world like a virus or other malware: it
infects every Event callback, every Promise, and every RxJS Observable. It
plugs in a bunch of its own guts in the middle of every bit of code
you try to run in an Angular app.</p>
<p>This &quot;infection&quot; is completely visible in any Angular production flame
graph. It changes and impacts every single execution stack in the
application. Look at the flame graph and the flames are all Zone.js.
<em>You may insert here in your mind an &quot;Everything is fine&quot; meme with the dog labeled Angular and
all the flames labeled Zone.js.</em></p>
<p>Somewhere in those Zone.js flames your code is probably running. Somewhere.</p>
<p>I captured some of these wall clock stalls in the performance tools. I
knew to expect most of the flame graphs to be Zone.js nonsense. I
assumed with the stalls taking minutes of wall clock time that
something not Zone.js should be visible enough in that haystack to
make a difference and make it possible to find.</p>
<p>I consistently found no needles in that haystack. I had minutes and
minutes of call stacks and the further I dug in the more it was all
Zone.js haystack and not a <em>single</em> needle of application code. Was the
performance problem entirely Zone.js? I had no good ideas from the
glimpses of internal-only Zone.js APIs and source files in the stack traces to
tell in any reasonable way whatever it thought it was doing. (I still
have no good ideas or answers months later. Zone.js remains a
terrifying horror mystery to me.)</p>
<p>At this point in the horror movie (Happy Halloween! I guess you can
now guess why I was maybe saving this story for this month; it's a
debugger's ghost story) several audience members would be shouting at
me: Zone.js has a debugger mode and turning it on is buried as a comment line in
the Dev environment.ts file in every template-scaffolded Angular
application, 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
environments spooky. It was at this point where I felt that I was out of debug
options and I needed that frightening last option. I cautiously opened
that last door.</p>
<p>With Zone.js in that weird debug mode, I could no longer reproduce the
pauses and the application performed better than production. 👻 Boo! It's
haunted! You're going to die! Get out of the house! 👻</p>
<h1>Zone.js Must Die</h1>
<p>This absolutely is one of my deepest nightmares as a sometimes
&quot;performance expert&quot;: the bad performance is coming from inside the
framework itself! The framework acts weirdly different in debug and
production environments and it's the production environment
experiencing the worse performance in a way that makes no sense. You
can't debug your way out of the production problem because your
debugger can't reproduce it.</p>
<p>Hyperbolically, I went <em>insane</em> here. I lost my damn mind.</p>
<p>I had hard evidence that Angular was a horror show under the covers
and was causing our production users real pain, anguish, and
suffering. But unfortunately, I don't have the power to convince an
entire company that the Sunk Cost Fallacy is real and less of a
problem than trying to keep sleeping in the haunted horror house
because we got such a good deal when we bought it from the previous
owners who died of mysterious circumstances that surely were unrelated to why the house was on sale.
I'm told to &quot;just do my job&quot; and patch a fix.</p>
<p>That left to me the only &quot;logical&quot; and &quot;pragmatic&quot; realization:
<strong>Zone.js Must Die.</strong></p>
<p>I suppose in the horror film analogy this is the realization by the
final girl that Zone.js really is some sort of serial killer and it is
time for her to roll up her sleeves and go on the offense and fight
back against that ruthless serial killer.</p>
<p>So I started researching everything I could to murder Zone.js without breaking Angular.</p>
<p>Angular is kind enough to give you the option to boot up with a noop
&quot;Zone&quot; and entirely disable Zone.js. Unfortunately, this breaks
Angular Change Detection in weird ways and most apps stop functioning
at this point if you just switch to the noop &quot;Zone&quot;.</p>
<p>Angular's Change Detection apparatus is a direct consequence of
Angular's broken compromises between providing RxJS Observables and
then also providing tons of imperative escape hatches. Observables are
entirely &quot;push&quot;: they push notifications when changes happen. You
shouldn't need change <em>detection</em> in a pure Observable world, you
already have change <em>notification</em> (&quot;for free&quot;), because that is what
Observables <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
of its promise: with &quot;free&quot; change notifications you can wire it to do
some very smart things also &quot;for free&quot;.) But Observables are &quot;hard&quot;
and Angular couldn't commit to them and the resulting worst of both
worlds compromise &quot;needs&quot; Change Detection.</p>
<p>That Change Detection uses Zone.js to tell it any time anything
happens in the app, ever. Zone.js figures this out by wrapping all the
Events, Promises, and Observables in the world that it can find with extra
instrumentation. Just to tell Angular &quot;hey, something changed
somewhere, I don't know, maybe&quot; (not even really what changed, certainly not to
the specific level of individual Observable pushes). Angular still has
to do a ton of work after those Zone.js callbacks to figure out what
exactly changed and then from there what to update in the
templates/DOM.</p>
<p>Fortunately Angular seems to have actually anticipated this, too, that
with Observables you have a &quot;push&quot; based system for notifications
already and in theory shouldn't need change detection at all. It took
me something of a deep dive into some of the less well documented
parts of Angular, but it turns out the framework indeed has left
component developers a &quot;manual stick shift&quot; option for writing
components: you can annotate in the Component decorator that the
component uses the Change Detection Strategy named &quot;OnPush&quot; and that
you will push all change notifications manually.</p>
<p>The &quot;<code>OnPush</code>&quot; Change Detection Strategy does give you an offense
strategy to use to fight Zone.js from the bottom-up of an application,
and it needs to be from the bottom up: <code>OnPush</code> components do not need
to be wrapped in Zones (and generally aren't, though Zone.js is
&quot;viral&quot; in nature and there are no guarantees it doesn't accidentally infect), which is great. But
that also means that <code>OnPush</code> components can only ever use other <code>OnPush</code>
components. Components that use the &quot;<code>Default</code>&quot; change detection
strategy and need Zone.js to detect their changes can use <code>OnPush</code>
components just fine, but not the other way around.</p>
<p>But a &quot;bottom up only&quot; hope in a brownfield application is still a ton
of hope to make a noticeable change. A &quot;manual stick shift&quot; option
isn't ideal, but that too gives hope that you have something that you
can automate and that you can build an automatic transmission on top of a
manual stick shift with software. It's not pretty, but it is
&quot;pragmatic&quot; and it will get the job done.</p>
<h1>Introducing the Pharkas Component Framework</h1>
<p>To recap: I lost my mind in horror. I decided that <strong>Zone.js must die</strong>. Then I
finally discovered some hope for a &quot;bottom-up solution&quot;.</p>
<p>I realized that I could build it: I could codify my &quot;Observables only&quot;
way of building components into a library, and use that library to
build a handy &quot;automatic transmission&quot; to replace Zone.js-based Change
Detection with something smarter and less compromised (if it sticks to
&quot;Observables only&quot;).</p>
<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,
reusable library for building (Zone-free) <code>OnPush</code> components in an
Observables only way that could provide not just automated push-based
change detection to Angular, but even bring in some of the &quot;smarts&quot;
ideas of &quot;Project Gawky&quot; and apply them as good defaults. By making
them good defaults I hope that my library can build not just a &quot;pit of
success&quot; but a &quot;pit of smart success&quot; to the developers that choose to
use it. For instance, React took several major versions worth of
revisions and refactoring and a lot of code to deliver &quot;concurrent
mode&quot; which deprioritizes most DOM work until after idle callbacks such as
<code>requestAnimationFrame</code> helping the browser to focus on interactivity
over DOM element thrashing. Concurrent mode is still not yet the
default in React for several compatibility reasons and needs to be
opt-in. I've built something similar in my own library for debouncing
change notifications to Angular to nice clean <code>requestAnimationFrame</code> time just using Observable schedulers in
very little code (it's probably a lot more documentation than code at
this point), and it is default and (simple) opt-out. (While it is at
it, the library also takes care of boring Angular administrative
trivia such as <code>ngOnInit</code> and <code>ngOnDestroy</code> lifecycle callbacks.)</p>
<p>Overall, I feel like this library has turned into some of the best
documented and well-tested open source I've had the pleasure to work
on. I'm not entirely satisfied with the testing just yet, as I'm
waiting for Angular to make the leap to the next major version of RxJS
to get some good &quot;marble diagram&quot; timing tests added. Because of that
useful default of debouncing to <code>requestAnimationFrame</code>, I need a marble
diagram harness that understands and fakes <code>requestAnimationFrame</code>
timing, which the next major of RxJS supports out of the box and I
wasn't happy with backports I attempted for the current Angular
supported RxJS.</p>
<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
Component Framework&quot;</a>. This name is a terrible joke, that is possibly
only funny to myself. I had lost my mind, remember, and I needed to
scrape out whatever sanity I could out of this entire horror
situation, <strong>and</strong> had to get whatever I built into production ASAP to make
users happy (and naming is indeed one of the hardest problems in all
of computer science). So I named it a joke and filled its README with
a few jokes to amuse myself. It's maybe not the most &quot;professional&quot;
approach, but sometimes we need humor in our darkest hours.</p>
<p>To entirely over explain the joke:
<em>Freddy Pharkas: Frontier Pharmacist</em> was a 1993 adventure game from Sierra On-Line near the
peak of their development golden age. It can be described as the
&quot;<em>Blazing Saddles</em> of videogames&quot; and is a joke filled satire of
cowboy, Western, and Old West tropes in which the title character just wants to
be a respectable, civilized Pharmacist selling prescriptions in a lawless frontier town.
(I recall it nearly breaks the fourth wall as hard as
<em>Blazing Saddles</em> as well, but it has been a decade easily since I last played
it. The comparison is not entirely unearned, for those that have a
high opinion of <em>Blazing Saddles</em>.) As someone trying to peddle RxJS
best practices in a sometimes lawless-feeling ecosystem, I sometimes
feel like a frontier pharmacist when working in Angular. (The terrible
pun there being that &quot;Rx&quot; in addition to technically meaning &quot;Reactive
Extensions&quot;, which was the original .NET name for its
Observables-pattern framework, is also one of the more common
abbreviations for the word &quot;prescription&quot; sometimes stylized ℞ and has been used by
pharmacists for that word for a long time, from latin &quot;recipe&quot; meaning &quot;take&quot;.)</p>
<h1>The Growing Pharkas &quot;Family&quot;</h1>
<p>Beyond the base component and the library that provides the core
&quot;<a href="https://worldmaker.net/angular-pharkas/">Pharkas Component Framework</a>&quot;, I've been slowly accumulating a lot of
ancillary libraries of other open source components and component base
classes that make sense to release next to it.</p>
<p>So far the biggest running theme of these other components is
providing Angular wrappers for &quot;Vanilla JS&quot; components. There are a
number of factors behind this including the sorts of components I've
needed to work on for my day job's production apps, navigating which
components are &quot;business critical/secret sauce/non-disclosable&quot; versus
which seem good candidates to open source (or clean room rewrite as
open source in my spare time, because I lost my mind and have done some moonlighting here) because they have no domain specific
code, and that the &quot;bottom up&quot; approach to converting to <code>OnPush</code>
components especially highlights your &quot;VanillaJS wrapper components&quot;
as a key &quot;bottom&quot; that needs conversion early.</p>
<p>I think &quot;Vanilla JS&quot; components (and components from outside
frameworks embedded inside Angular) are especially ripe to gain the
benefits of <code>OnPush</code> style components: they <em>shouldn't</em> have any
change detection needs because they handle everything internally.
Wiring all of a &quot;Vanilla JS&quot; component's Event handlers, Promises, and
even Observables with Zone.js just because it may in very unlikely
cases result in a change to detect is possibly the purest example of
obviously unnecessary overhead. Zone.js <em>tries</em> not to be <em>that</em>
&quot;viral&quot;, and most existing Angular wrapper components know the pain of
what that means and all the little things that need to be wrapped in
an <code>NgZone.run</code> callback. (<code>Default</code> components using <code>OnPush</code>
components don't need <code>NgZone.run</code> callbacks in my experience, that
boundary is handled automatically enough, unlike the &quot;Vanilla JS&quot;
boundary.)</p>
<p>I think these Pharkas &quot;family&quot; of &quot;Vanilla JS&quot; wrappers should serve
as useful examples of the gains to be made in using <code>OnPush</code> component
wrappers in all cases. There should be no doubts that the performance
is better in the boundary spaces between Angular and not-Angular.
There's no <code>NgZone</code> injections and no <code>NgZone.run</code> calls. There's no
change detection notifications necessary at all when the component
handles all of its own update cycles.</p>
<p>I think they also serve as good, interesting examples of the types of
setup and teardown you can do when you think entirely in Observables.
I think that's often one of the things developers complain the most
that they need imperative &quot;escape hatches&quot; from Observables for (and
why Angular is the bizarre compromise that it is): dealing with the
boundaries between components that understand them and those that have
more imperative APIs. You can do a lot with Observables if you put
your mind to it.</p>
<p>These libraries are also more documentation than
code. Some of them are direct drop-in replacements for well known
Angular wrapper libraries and I think you'll find less, easier to
understand code than the wrappers that they replace, even before you
add in the additional benefits that they simply perform better.</p>
<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
&quot;Vanilla JS&quot; components themselves (more than one!) used in a combined
&quot;dashboard&quot; with (fake) real time data. I'm incredibly biased here,
but I have never seen performance that strong at the &quot;Vanilla JS boundaries&quot; anywhere else in the
Angular world. The real time is &quot;fake&quot; but modeled at speeds and
amount of data I've seen in actual real time dashboards in Production
(in Frameworks that are not Angular). There's definitely no strange
and unexpected Zone.js stalls. (The demo site is built in the Angular
noop &quot;Zone&quot; so truly has no Zone.js at all even in accidental
fallback.)</p>
<p><a href="https://github.com/WorldMaker/angular-pharkas">All of this is MIT licensed open source</a>, and I encourage everyone to
at least dig in and glance at the source and maybe try to learn from
it, if nothing else, even if you don't think you need any of these
libraries in your own production work.</p>
<h1>Aside: Observable &quot;State Management&quot;</h1>
<p>The &quot;<a href="https://worldmaker.net/angular-pharkas/">Pharkas Component Framework</a>&quot; is agnostic to how you build
Observables, it only mandates that you <em>use</em> Observables.</p>
<p>There are a lot of options in Angular, many inspired by the React
ecosystem's Redux (in my opinion without understanding the reasoning
behind Redux, but that is a complaint for another blog post) such as
NgRx, NgXs, and more. Pharkas doesn't care if you use any or none of
them. It works well with them. It works well without them.</p>
<p>In my company's production we already have a wild hodge podge of all
of the above. In my own development and prioritization I've taken a
&quot;without them&quot; approach I currently call &quot;lots of small Observables&quot;
which may be possibly called &quot;Atomic Observables&quot; in analogy to the
React &quot;counter-Redux&quot; term &quot;Atomics&quot;. Today I don't have a library to
offer on this pattern. I see it as simply a pattern and an &quot;obvious
one&quot; at that, so I don't think it needs a library at all. (I might
even describe it today as a &quot;natural&quot; pattern of Observable building,
implying it is the NgRx/NgXs/et al of the world that is perhaps a bit
&quot;unnatural&quot;.) It is on my TODO list to eventually try to write some
better documentation on that pattern in the hopes it sparks joy in
some developers.</p>
<p>At one point I thought Pharkas &quot;needed&quot; a state
management answer or to be a little bit less state management
&quot;agnostic&quot; to be a &quot;real&quot; Angular Observable library (thanks NgRx/NgXs
et al for that bit of impostor syndrome), but I decided YAGNI (you
aren't going to need it) and yeeted it out in early versions and have
no regrets having done so.</p>
<h1>Should You Use Pharkas?</h1>
<p>I probably wouldn't if I were you. You likely have no good reason to
trust my claims at face value and doubt my credentials. I probably
wouldn't even build this library, much less offer to maintain it if I
convinced my day job to avoid Angular like the plague, which I have
tried to convince them multiple times. I don't blame you to be
skeptical.</p>
<p>Highlighted and self-aware summary of previous sections:</p>
<ul>
<li>Zone.js was built by developers in the lovely ivory towers of
Google. They were confident enough in that effort that they proposed
that to actual standards bodies as a way that all future browsers
should work in perpetuity.</li>
<li>The Pharkas Component Framework was written by an impostor
syndrome-filled dark matter &quot;Enterprise&quot; developer in a company that
is not primarily a software company.</li>
<li>This developer openly admits in a blog post that the framework came
out of a fit of insanity.</li>
<li>The Pharkas documentation files and even project name are full of
unprofessional jokes.</li>
<li>The Pharkas developer and maintainer has admitted to hating the
Angular Framework in blog posts and claims that they will drop
maintenance support at first chance, depending on their day job's
needs. (On the other hand, <a href="https://github.com/WorldMaker/angular-pharkas">it is MIT licensed Open Source on Github
and easy to fork</a>.)</li>
<li>Pharkas doesn't provide any sort of &quot;State Management&quot;.</li>
</ul>
<p>I scratched my own itch here. I solved some critical production
problems that needed solving ASAP, somehow or another. I did what I
had to do. This blog post isn't a plea to use this work. I have made
promises that I think you would see clear performance gains and
kinder, gentler developer experience if you to do that,
but I don't expect you to take my word for it.</p>
<p>What I would like? Please learn from it! <a href="https://github.com/WorldMaker/angular-pharkas">It's a handy, easy to explore
MIT-licensed Open Source repository.</a> If there's one particular
takeaway here: use <code>OnPush</code> components everywhere you can! This truly is
a bottom up initiative. It needs to be &quot;grass roots&quot; in Angular, because it is not the default. Component
developers (<em>especially</em> those wrapping &quot;Vanilla JS&quot; components) should
start leaning <em>OnPush</em> component by their own default choice. I hope no one else
experiences the debugging ghost story 👻 I did to the same extent and it
truly is as rare as it seems in Angular that more people don't call
Angular the &quot;haunted house framework&quot; or worse, but after having experienced
that every library I see in Angular I now evaluate on &quot;does it use
<code>OnPush</code> components?&quot;. You don't need <a href="https://worldmaker.net/angular-pharkas/">Pharkas</a> to build <code>OnPush</code>
components. I think Pharkas makes it very easy to do that, and adds
some nice &quot;automatic transmission&quot; and smarts on top of it, so I would
recommend taking a look using it to build your next components, but
again my plea here is only for using <code>OnPush</code> components, I don't care
how you get there (&quot;manual stick-shift&quot; or not).</p>
<p>I hope I have set a good example here.</p>
<p>Happy Halloween, and good luck if you are an Angular developer. 🎃</p>
]]>
      </content:encoded>
      <category>coding</category>
      <pubDate>Sun, 30 Oct 2022 00:00:00 GMT</pubDate>
    </item>
    <item>
      <title>Don't Grow Old</title>
      <link>https://blog.worldmaker.net/2022/08/17/dont-grow-old/</link>
      <guid isPermaLink="false">https://blog.worldmaker.net/2022/08/17/dont-grow-old/</guid>
      <content:encoded>
        <![CDATA[<p>I wrote what follows this foreword on July 9, 2018. I was sitting with my sister at the bar at the
restaurant where my brother worked. It was some of the earliest evening hours the restaurant was
open and there was hardly anyone there. We both were drinking as we were waiting to catch my brother.
Our grandmother had passed away. We were there for each other, to spread around some hugs, and to
cry into some bourbon, as just one stop in a busy grief tour that always used to be expected to come
from a family's funeral week. (2020 told us not to even take <em>that</em> for granted, of course.)</p>
<p>The title of this essay came easily at the bar that evening (though it still felt like afternoon)
and I recall the text flowed out quickly behind it. I think I finished it before I finished my
bourbon that afternoon. That's how I remember it at least.</p>
<p>My father believes a lot in sharing (and preserving) memories and especially loves to fill times of
grief with impromptu group circles of memory sharing. Most usually in such situations I feel under-
prepared like I forget to do the homework and I find it stressful and forget any useful memories and
rarely have much to add. I appreciate everything about it, and I love my father for it, even if I do
slack off on the &quot;homework&quot;.</p>
<p>This essay felt to me like one of the first times I was ever prepared with the homework, and yet the
opportunity never quite arose to read it publicly. That's what I had written it for. Some of that is
certainly on me for not speaking up louder that I had something prepared and wanted to read it. Some
of that is just the blur of all of that and way that grief gets processed and everything else going
on. It's funny in its own little way that the one time I felt prepared was a rare time where I didn't
feel I needed to be.</p>
<p>I'm sure I'm still processing this grief, as it's been a weird four years. I thought something that
may help a tiny bit is the small closure that would be from at least publishing it publicly, since
it was intended as a public address, though presumably in a admittedly in more intimate setting like
a family dinner. There two it's funny in its own way that actually doing that in the moment felt
scarier than publishing it in the somewhat less intimate setting of a blog post.</p>
<p>Other than adding this foreward, I've decided to leave this essay almost entirely as it has been
sitting in my notes for four years.</p>
<hr>
<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>
<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>
<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>
<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>
<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>
<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>
<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>
<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>
]]>
      </content:encoded>
      <pubDate>Wed, 17 Aug 2022 00:00:00 GMT</pubDate>
    </item>
  </channel>
</rss>