This post is going to be full of things that are "spoiler shaped" 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 "spoiler shaped" 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.
At the end of Season 2 of Loki 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.
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 might 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 Endgame and make it seem like the whole thing had followed a single, well-organized plan all along.
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 "superhero fatigue" and feel tired about it all. Even the usually effusive Mikey of Movies with Mikey on the channel Filmjoy, a channel whose very mission is to seek joy in films rather than cynicism, eels doubt that Avengers: Doomsday may be the doomsday of the MCU, and expresses some sense of "superhero fatigue" and expects to fall off the ride soon, probably at Doomsday.
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 Filmjoy (YouTube), 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.
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 "homework" will pay off. All of that is a hard, strict pressure on the movies, a demand for their shape and their contents.
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 Thunderbolts Asterisk Colon Asterisk The New Avengers 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 "superhero fatigue" such as concern/angst/FOMO that you haven't/won't/can't "do all the homework": 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 least fun and I don't think that's entirely a coincidence. There are payoffs and rewards and callbacks still, but some of them hurt, because they are so chaotic.
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 seem 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.
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 "Who?" then "Congratulations you may have already won", 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.)
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 not 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 "doing all the homework" to believe this about Kang, but I believe it is a rare opinion still. Perhaps I did extra credit, I don't know yet.
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 The Kang Dynasty. Kevin Feige has lied about subtitles to Comic Con crowds before. Captain America: Civil War was originally Comic Con announced as Captain America: Serpent Society to avoid some types of spoilers. Even more recently Disney's massive marketing engine was hilariously "wasted" on months of selling a movie as just Thunderbolts Asterisk for a very last minute theatrical reveal that it was in fact full titled Thunderbolts Asterisk Colon Asterisk The New Avengers. What a silly, chaotic prank, I thought that that was great.
Per the wikis (since I read fewer comics than I'd like), The Kang Dynasty 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 Age of Ultron needed to get from comics page to screen. It was also a terrible title and had unfortunate aspects of ancient "yellow fever" 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 "classic" 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.
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 Doomsday. 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 The Kang Dynasty 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).
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, the 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 The Hulk, the MCU can recast anyone. If Kang mattered and The Kang Dynasty was the real plan, they'd have no difficulty recasting him. (Especially because "Multiverse Shenanigans" 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.
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 "need" to be recast?
The titular Kang the Conqueror shows up in one and only one place across both Phases 4 and 5: Ant-Man and The Wasp: Quantumania. 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 "Honey I Shrunk Myself" 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.
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 the next movie 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 do with the next production. They are punchlines. They are truly meaningless homework, almost each and every single one of them.
So many of my friends upset about the "I thought the next Avengers movie was going to be The Kang Dynasty" 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. Imagine 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 Infinity War. It is 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 Quantumania came out. I still find it hilarious now.
If you are only watching the movies Kang the Conqueror is explicitly a joke.
But we're not only watching the movies, we're doing all the "homework", so we have to talk about the Disney+ TV show Loki. Kang the Conqueror is not in Loki. You can skip the rest of this with the answer that Kang the Conqueror is still a joke in the MCU, trust me.
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 itself a joke. The big bad, after many twists and turns (the journey is more fun than the final "twist" here!) is a stupidly monologuing Majors originated character with the silly name "He Who Remains" doing his best impression of The Architect from The Matrix and mansplaining the multiverse to everyone, but also especially Loki. He claims to be the good guy, of course, and that his goal is "simply" to protect the multiverse from even worse copies of himself, specifically Kang the Conqueror. (Ooh, name drop easter egg, wow.) This is all 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 The Matrix film I mentioned and you're a fan of multiverses and you're not a fan of mansplaining, it certainly feels like "He Who Remains" 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 She-Hulk TV show for more fun on that subject.)
In the second season we get a more interesting Majors originated character, another "Kang Variant", 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 "He Who Remains" and becoming "the god of stories", replacing a "benevolent" dictator of order and a single easy to digest timeline with a god of mischief and chaos and a messy, unruly multiverse.
I find it especially useful to note that this Loki Season 2 story is the exact reverse 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.
Kang the Conqueror is a joke and has already 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 Avengers: The Kang Dynasty turned out to really be Avengers: Doomsday.
There's one more piece of comics homework involving Kang the Conqueror with respect to both Fantastic Four and the Young Avengers, and Fantastic Four: First Steps 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.
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 question mark 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.
I unrepentantly love Quantumania. 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 Captain America: The Musical featured in the Hawkeye show, not that Hawkeye is "required homework" to enjoy the opening of Quantumania, 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.
One other thing I enjoyed about Quantumanania 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 "Offenders" TV shows. (Netflix got the street level Defenders, an erstwhile collection of heroes that defend New York City, with an eventual "team up together" 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 "What if Robot Chicken had a budget to do an entire show just about MODOK wanting to live a normal family life?" which is a ridiculously silly premise and the whole thing is a silly, fun time (with Patton Oswalt!).
It doesn't exactly work in Live Action. Nobody expected it would. Quantumania has fun with it and it is chaotic fun and I think the important bit is that they tried. 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 "realistically". It is first and foremost a comic book character. My favorite part to the MODOK appearance in Quantumania is that they stunt cast Bill Murray seemingly just to have one of the few living actors that could straight man deliver with any semblance of gravitas the phrase "Mechanized Organism Designed Only for Killing". 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 quickly 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.
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 comic back in comics for me there.
If there is one Disney+ show that you "must" see, according to a lot of the homework suggestions, it is not Loki, it was WandaVision. WandaVision was great, chaotic fun. If it did anything particularly well, it completed an arc from Ant-Man and the Wasp 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.
WandaVision 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.
WandaVision is also the "required" homework that makes Doctor Strange and the Multiverse of Madness weirdly harder for some to watch. The reason for that is text in the show, and repeated text in the sequel show Agatha All Along (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 "main character" in a TV show is always the protagonist and can't be the antagonist. Especially very easily missed if you think the "main character" of WandaVision 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.
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 "you'll have to come back next week" 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 WandaVision is that in grief after the death of Wanda's love, Vision, Wanda, the 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 "all" of the homework, you'd even recognize the MCU's most evil book from Agents of SHIELD and its multiple 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 WandaVision and Agatha All Along 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.)
But even in the text of WandaVision strictly by itself and especially with the not so subtle horror cinematography around the book every time it is shown in WandaVision, it should be clearly an "evil book" whether or not you also pick up it is "the most evil" because that isn't directly in the text.
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 "most evil Mountain" and "most evil Book" was so hard to understand that Wanda's takeaway from WandaVision was absolutely not "time to go back to heroing" but definitely "let's get serious about doing the evil on purpose, not just by accident".
Wanda is the start-to-finish villain in Doctor Strange and the Multiverse of Madness (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 "her love was killed and she spiraled into crazy grief that she could not escape" 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 Spider-Man 2 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.
If you came into DSMOM thinking the finale of WandaVision 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 shock. A beautifully chaotic prank. The seeming protagonist of that show you enjoyed went full chaotic evil and you didn't suspect the "happy ending" was anything but. It was delightful chaos. Wanda truly is the Chaos Witch.
DSMOM is better if you didn't do "the homework" first. WandaVision 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.
(Semi-related aside for bonus extra credit: Seanan McGuire wrote a full novel called "What if… Wanda Maximoff and Peter Parker were siblings?" 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 "What If…?" 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.)
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.
In doing this the MCU taught a lot of us that the after credits stinger was "important" and sometimes a major lore drop.
Especially from hindsight, almost all of the Phase 4 and 5 after credits stingers seem like punchlines and jokes instead of Phase 1-like "critical setup". They have much of the same shape as the "lore full" 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 "oh wow, Nick Fury!" moments as they are "wait who is that and why do they matter?" in every case. Almost no one is going to go "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!" I think 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 "homework". The "Who?" audience reactions to post-credits stingers were some of the funniest in Phase 4, in hindsight after Phase 5.
Starting with WandaVision, Disney/Marvel shifted its TV strategy from ABC (RIP Agents of SHIELD) 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 "homework" 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 "will have anything to do with the movies". (Wonder Man was delightful and had this extra bit of distance as a "Marvel Spotlight", which is a shame because it played off some fun in Shang Chi and hopefully we'll get more Wonder Man, we can hope even in a movie.)
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 "canon". 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 Wonder Man.) I've already mentioned The Offenders more than they warrant. I'll miss the chance here to write more about how delightful X-Men '97 and Moon Girl and Devil Dinosaur are to different parts of my inner child, but it will also save me from struggling to find something nice to say about Your Friendly Neighborhood Spider-Man or Marvel Zombies.
I've covered most of the drama and the "too much homework" problems. The two next hottest takes I have are Eternals 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 real Celestials in Live Action, ahead of Fantastic Four: First Steps which owed it thanks for the homework (hurrah, no more boring space clouds! let's get that Kirby Kosmic weirdness, yes please) and also that The Marvels was nearly perfect and too many people slept on it.
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 Endgame 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.
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 Daredevil: Born Again but no confirmed dates (and just as likely to slip to Phase 7 timelines?), and a date for VisionQuest which so far doesn't sound that exciting on paper. That's it on Disney's current announced schedules for Phase 6.
Disney rearranged a bunch of dates for Phase 7 projects in March which led to a lot of "Phase 7 movies confirmed" style posts, but really all anyone can say is "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".
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 "magic" way like Avengers: Endgame 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 "homework" and perhaps that's also why I haven't really felt "superhero fatigue". (I may have also been inoculated against "superhero fatigue" 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 Avengers: Doomsday 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 "how does it all connect?" I'm still very curious to see what the next few movies do, but I've enjoyed the ride so far along the way.
]]>"Lab Director, would you mind taking a listen to this call we found?"
The earnest appearing tech at my office door seemed shy, but excited.
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, "What sort of call?"
"Some of the offices forwarded over some sort of strange phone call we picked up being sent to the weird Area Code 347."
"What is unusual about that area code?" Sure I work for AT&T, but I'm a lab director in the research labs, I don't know everything about phone dialing.
"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."
"So it's a garbage call? Noise in the system?" I might as well start with the obvious questions.
"That's why we'd like your ears on this, Director, before we go chasing ghosts in the system."
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.
Having heard it I took in the name of the workspace and addressed the tech, "That sure is interesting for garbage, Martin."
"There's one more twist; there were three more calls after this one to the same number."
I sighed, of course there was more to this weird rabbit hole. "What are your working theories, Martin?"
Martin scratched his chin, "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."
I grimaced, "Let's try not to get the military involved just yet. Can we slow this down, get printouts of it, somehow?" 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.
"It's too fast."
"It is too fast?"
Martin looked almost upset, "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."
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, "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."
Back at my desk I dialed my "friend" 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, "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."
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, "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."
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.
Bob showed up to a conference room a couple weeks later with printouts and a somewhat angry expression, "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."
"ASCII?" 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.
"At IBM we encode text in a well designed encoding we call EBCDIC. You all at AT&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."
Bob pointed to highlighted sections in the ASCII decoded document including "u up?", "babe?", "talk to me", "plz".
Bob turned to leave, but then turned back, "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&T dig up a 25 kilobits per second telegraph?"
Martin almost choked on whatever warm beverage was in his mug, "25 kilobits per second? I knew it was fast, but that's impossibly fast."
"Roughly," Bob shrugged. Then Bob's expression changed again to something like fascinated curiosity, "Wait? This isn't a prank? This really is some sort of alien telegraph?"
I glanced to Martin and back to Bob, "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."
Martin returned to my office in a few days with a bunch of analysis and a few other techs, "We've got a lot of notes and some wild theories," he began and I felt like I was all ears.
He starting pointing to repeating patterns near the visible English words, "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."
"Future punctuation? From the future? I think you need to walk me through these wild theories a bit more," I had no idea what to think.
"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," Martin looked almost scared to admit the theory had started from him, "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."
"Okay, timestamps, so these are definitely from the future?"
Martin nervously started rambling, as I find so many good techs are easy to fall into, "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—"
"Right, we've covered the timestamps. They are at least 54 years after some point we don't know," I tried not to betray any impatience in my voice, I just was hoping to short-circuit an accidental loop. "What's 'message switching' and why does its metadata matter?"
Martin took a deep breath, "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."
"So we expect that sort of message switching to be more than 14 years out?"
"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."
"We're on–?"
"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?"
"But in the future they've reverted to some sort of telegraph?" I was starting to wonder if Bob was maybe right that this was some sort of prank, but not on him.
"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." Martin was excited like a puppy, "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."
The enthusiasm was fascinating and almost infectious, but I was supposed to be wearing a business-minded hat on this side project, "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?"
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, "So we were thinking that we should respond to these telegraphs."
"You want to try to send telegraphs to the future?" That certainly wasn't where I was expecting this to go.
"Think of what more we could learn!"
"Wouldn't that be dangerous to the timeline if we learn something out of order?"
"Weren't you just asking if we could reverse engineer tech out of this?"
I took a few seconds to gather my fury about that response, "Fine, but do we even have the ability to send messages back?"
Martin indicated the pair that had accompanied him, "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—" Martin stopped and again looked for some sort of moral support from his peers.
I waited patiently.
Martin finally took a deep breath, "It's still too fast for us. We'd need some help."
I sighed, "You want me to waste more money on IBM computing time?"
Martin nodded and I worked on a plan to butter up Bob to help us send an "alien telegraph" message. I realized it probably meant planning on a few rounds of golf with that lout and his usual crew.
I was surprised that Bob seemed almost eager to help on this next step, despite having been angry it wasn't a good, "proper" 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.
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 "Who is this?"
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.)
Response message 1 was "y shout?" and message 2 was "u srsly gonna new phone who dis me?" Both had plenty of "future punctuation" 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 "future telegraph" 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.
Martin boiled down the working theories on "the shout problem" from Response message 1 for me a few hours after a debate between him and one of the IBMers got a bit heated, "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."
"What's the theory?"
"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."
"Why would case matter?"
"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."
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 "alien punctuation", 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 "alien punctuation".
Even with "the shout problem" 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 "New York City" whether or not there were "aliens" 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.)
Our weeks of deliberations were interrupted by Response message 3 arriving. It was simply "c any gud films l8ly?" with no future punctuation at all. Martin, at my behest, worked very hard not to rub it into the IBM faces that the "alien punctuation" was neither necessary nor did a lack of them imply "shouting".
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, "good films" 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 "gone with the wind", emphasizing the lower case text as we'd all agreed on after "the shout problem".
Response messages 4 and 5 were also received in short succession. Response message 5 was a baffling "tbh bae idgaf" 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.
Where Response message 2 had felt like a small win for us on the "phone teams", 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 "straight talk" out of him on the subject. With a couple of cocktails and a good few holes below par he was surprisingly forthcoming to me, "We've been calling Binary Object Alpha the 'future photo'."
I couldn't help but grin, "So you agree with us now that this is probably future communications rather than alien communications?"
Bob was surprisingly dark and sober seeming, despite my having loosened him up a bit, "Yeah, there doesn't seem to be any remaining question on that on our side."
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.
Finally he bit into the silence, "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&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&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&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."
I thanked Bob for both the information and the warning that things were about to get more complicated.
Eventually I circled back around to the work conversation, "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?"
Bob hesitated in a manner that I didn't like, "So we may have inadvertently gotten the military involved."
"What?"
"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."
"Without other context?"
"Uh, probably."
"Bob! Get me the name of your military contact you dumped that problem on."
I had Martin help lead an effort to draft a clean executive summary of the messages so far, our findings and speculation. The next "committee for future communications" 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.
Collins glanced at the assembled rag tag group, "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?"
Martin tried his best Clark Gable impression, "Frankly my dear, I don't give a damn." That obscenity has been the talk of the town.
Collins pointed to Response message 5, "To be honest, babe, I don't give a fuck." Someone gasped at the elevated obscenity. A couple of the committee groaned at not getting it before when it seemed so obvious now. "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." 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.
It took a few more days of committee debate to find a response that we all hated the least ("funny"), but just before we could send it we got Response messages 6 and 7 in short succession "DON'T GHOST ME" and then "fine, blocked u jerk". 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&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.
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.
]]>"AI-Driven Innovation" is an investment in a rental economy of "legacy code as a service". Legacy code is always an indicator of some of the most risk adverse pieces of a company's software portfolio.
I have spent so much of my career in "the legacy code mines". 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 "retirement" plan was to find a cushy high paying, low effort COBOL job.
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.
I've been accused about being overly unfair to LLMs by calling their output "legacy code as a service", 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 "someone else's legacy code" or at least in the most generous cases "some other business' complement".
If AI can't train on the innovative software, how can it help you find innovation in your own software strategies?
Show me the part of the LLM family of algorithms that is the "creativity" and I can show you a slot machine and try to teach you about the Gambler's Fallacy.
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 "surprise" that is really just random variation on a theme (and eventual regression to the mean).
You could put a lot of creativity into the input of the system, so called "prompt engineering", but what good is laundering that much creativity through a machine designed to produce the most statistically average outputs from it?
That of course entirely relies on people seeing "prompt engineering" as a creative tool for innovation. Most of the companies seeking "AI Innovation" wrote off software as "overhead" and "cost centers" 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. "AI-Driven Innovation" 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.)
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 that 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.
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 "that developer" 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 "version to version". 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.)
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 "No Warranty" 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 "warranty" or "support contract" 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 "licensed" or how many "No Warranty" clauses are between you and whoever originally owned the code it just output. There's not just "no owner" there is a multi-layer onion of "negative ownership". Your EULA with the LLM declares "No Warranty". Most of the code the LLM trained on declares "No Warranty". Who really owns anything? It's all just a rental to some landlord, as a service.
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 "copyleft" licenses designed to legally encourage "what you take from the open source community you volunteer to bring back and continue to enrich the community with your own contributions". 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 "this is an illegal slaughterhouse" and "this is a health code violation" and "this is just generally a violation of common sense human decency".
Is that a great foundation for "AI-Driven Innovation"? Does a nasty-smelling onion of "no one owns this code", "no one wants to know what the real ingredients in this code are", and "if they did know all the ingredients in this it would be illegal" sound like "innovation"? 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.
"AI-Driven Innovation" 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 "innovation" to be found in the output. It's going to keep checking boxes in PowerPoint slides to an investor class that has bought into "AI-Driven Innovation" 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.
In the mean time, the truly innovative, experienced software engineers are encouraged to accept layoffs if they can't "align" on "AI-Driven Innovation". (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, "AI-Driven Innovation" is crafting a mesmerizing illusion that software as a labor practice is over and companies don't really need software developers at all.
Garbage in, garbage out, in a feedback loop threatening to spiral to destroy real innovation in software development.
]]>(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.)
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# "view state" model, for projects that want a ReactiveX-"native" alternative to state management and/or event bus patterns such as MVC, MVVM, Mediatr.
RootActionThe core type for this Redux design is RootAction. This is not a
particularly great name, but it does avoid conflict with
System.Action<T> (the void-relative of System.Func<T, U>). In
proper Redux fashion, this is a very simple type with only a required
field name Type.
public record RootAction
{
public virtual string Type => $"{GetType().Name}";
}
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.
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 Type seen here with something more useful to
System.Text.Json's polymorphic support.
The advantage to this form of base Type implementation was that a tree
of types was relatively easy to build. Example:
public record CoolFeatureAction : RootAction
{
public override string Type => $"CoolFeature/{GetType().Name}"
}
public sealed record CoolFeatureInitialized(bool IsCool) : CoolFeatureAction;
One of the things that this also highlights is that the Redux world best practice of writing "action creators" is handled automatically by C# record syntax. The primary record constructor is already laid out as a Redux-like "action creator", and provides similar refactoring benefits.
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 "user interaction that
just happened" in the past tense. Actions should generally not be "mere
setters" (FieldASet, FieldBSet) but rather State transactions as
a high-level event (EntireFeatureInitialized with everything needed
to update FieldA and FieldB all at once).
Another brief digression from JSON polymorphic debates was if the
RootAction's Type property implementation should have been, assuming
Reflection was still fine:
public record RootAction
{
public string Type => $"{GetType().FullName.Replace('.', '/')}";
}
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 Replace being to still prefer the Redux dev tools suggested /
namespace separator instead of .NET's ..)
In part from references libraries that heavily relied on .NET's value
types (typically, but not just, readonly record struct) to deeply
avoid nulls (at the risk of sometimes equally confusing "zeroed"
default(T) from structs) the application had picked up the premature
optimization of using value types for a lot of internal state data.
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 by copy 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).
Don't forget that public record is shorter than
public readonly record struct 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.
The heart of the Redux pattern is Reducers to apply Actions to State. In
C# we used a very simple IReducer definition from which a number of
things inherited, include a Reducer<Action, State> base type for single
Action reducers.
public interface IReducer<State>
{
public State Handle(RootAction action, State state);
}
public abstract class Reducer<Action, State>
: IReducer<State> where Action : RootAction
{
public abstract State Reduce(Action action, State state);
public State Handle(RootAction rootAction, State state) => rootAction switch
{
Action action => Reduce(action, state),
_ => state,
};
}
The base class is a pattern in C# you also see in things like Authorization
Policy Handlers, a type-safe "trampoline" from the generic RootAction
to a specific Action (we had less of a problem with a type parameter
sometimes shadowing System.Action<T> than a top-level type doing so).
(This is also Typescript influence here of dropping the T-prefix
semi-Hungarian notation for type parameters; though as with most C#
naming schemes the I-prefix for interfaces is maybe eternal at this
point.)
Because the trampoline is often the shortest way to write many Reducers,
the IReducer<State> method borrows the name Handle from places like
Policy Handlers so that Reducer<Action, State> can use the more domain
friendly Reduce as its more specific abstract function.
It's also important to note that IReducer<State> is intentionally and
only synchronous. That's an ancient Redux best practice. That will also
be something we return to as we get to Epics.
To round out our CoolFeature example we might have an example Reducer
like so:
public record CoolThing(string Name, /* … */ DateTimeOffset Created);
public record CoolFeatureState(
bool IsCool,
DateTimeOffset CoolStarted,
IEnumerable<CoolThing> CoolThings)
{
public static readonly CoolFeatureState Disabled = new(false, DateTimeOffset.MinValue, []);
}
public class CoolFeatureInitializedReducer(TimeProvider timeProvider)
: Reducer<CoolFeatureInitialized, CoolFeatureState>
{
public override CoolFeatureState Reduce(
CoolFeatureInitialized action,
CoolFeatureState state) => state with
{
IsCool = action.IsCool,
CoolStarted = timeProvider.GetLocalNow(),
// ASIDE: Injecting a TimeProvider is a great way to increase
// unit testing powers of this reducer because you can test in
// fake time with a FakeTimeProvider.
};
}
public static class CoolFeatureExtensions
{
public static IServiceCollection AddCoolFeature(this IServiceCollection services) => services
.AddTransient<IReducer<CoolFeatureState>, CoolFeatureInitializedReducer>();
}
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 synchronous.
Lens<A, B>Before we can talk about the Store and Slices, we need one more tool in our toolbox.
In Typescript, ala the original Redux pattern, we can easily "slice" 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
T[keyof T].
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 getter from one item to another (given an
A, I want a B), and a type-safe setter in the other direction
(given a B, I would like an updated A).
There are several functional programming libraries that provide a
Lens<A, B> useful in C#, including LanguageExt, but Lens<A, B>
really is just as simple as described, so we can also just implement our
own quite simply:
public record Lens<A, B>(
Func<A, B> Get,
Func<B, Func<A, A>> SetF
)
{
public A Set(B value, A container) => SetF(value)(container);
}
There's a few other convenience functions you could implement (often
named Update), but that's the important heart of Lens<A, B>.
The importance of SetF in this form as Func<B, Func<A, A>> as the
underlying setter 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 Lens<A, B> generically useful to
several of our needs.
One useful way to see it from Redux points of view is that
SetF(someFixedValue) creates just about the most simple type of Reducer
possible (given a State, yield the next State). It's a handy abstraction.
ShareReplayWe 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:
public static class ObservableExtensions
{
public static IObservable<T> Share<T>(this IObservable<T> source) =>
source.Publish().RefCount();
public static IObservable<T> ShareReplay<T>(
this IObservable<T> source,
int bufferCount) =>
source.Replay(bufferCount).RefCount();
}
The RxJS names are little more evocative to me than remembering when to
use RefCount().
ISlice<State>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.
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 slice of your Store.
We start with the general interface design:
public interface ISlice<State>
{
public IObservable<State> States { get; }
}
public interface ISubSlice<ParentState> : IReducer<ParentState>
{
public void RegisterParentSlice(ISlice<ParentState> parent);
}
ISubSlice<ParentState> 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.
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 IEnumerable<T> to get all implementations of an
interface that have been injected.
public class Slice<ParentState, State>
: ISlice<State>, ISubSlice<ParentState>, IDisposable
{
private readonly IEnumerable<IReducer<State>> reducers;
private readonly Lens<ParentState, State> lens;
private readonly BehaviorSubject<ISlice<ParentState>?> parentSlice = new(null);
public IObservable<State> States => parentSlice
.Select(parent => parent is null
? Observable.Empty<State>()
: parent.States
.Select(lens.Get)
.DistinctUntilChanged())
.Switch()
.ShareReplay(1);
public void RegisterParentSlice(ISlice<ParentState> parent) =>
parentSlice.Next(parent);
public Slice(
IEnumerable<IReducer<State>> reducers,
Lens<ParentState, State> lens
)
{
this.reducers = reducers;
this.lens = lens;
foreach (var reducer in this.reducers)
{
if (reducer is ISubSlice<State> subslice)
{
subslice.RegisterParentSlice(this);
}
}
}
public ParentState Handle(RootAction action, ParentState parent) =>
lens.Set(
reducers.Aggregate(lens.Get(parent), (acc, reducer) =>
reducer.Handle(action, acc)),
parent);
// or with an Update helper implemented:
// lens.Update(parent, parentState =>
// reducers.Aggregate(parentState, (acc, reducer) =>
// reducer.Handle(action, acc)))
public void Dispose()
{
GC.SuppressFinalize(this);
parentSlice.Dispose();
}
}
public static class SliceExtensions
{
public static IServiceCollection AddSlice<ParentState, State>(
Lens<ParentState, State> lens) => services
.AddSingleton(provider => new Slice(
provider.GetServices<IReducer<State>>(),
lens))
// allow direct injection IObservable<State>
.AddSingleton(provider => provider
.GetRequiredService<Slice<ParentState, State>>()
.States)
.AddSingleton<ISlice<State>>(provider => provider
.GetRequiredService<Slice<ParentState, State>>())
// register for pickup by its parent
.AddSingleton<IReducer<ParentState>>(provider => provider
.GetRequiredService<Slice<ParentState, State>>());
}
Our CoolFeatureState might be part of a larger RootState:
public record RootState(CoolFeatureState CoolFeature)
{
public static readonly RootState Initial = new(CoolFeatureState.Disabled);
public static readonly Lens<RootState, CoolFeatureState> CoolFeatureLens =>
new(state => state.CoolFeature, state => coolFeature => state with
{
CoolFeature = coolFeature,
});
}
With Slice implemented we can fill out our example CoolFeatureExtensions
with the next registration piece:
public static class CoolFeatureExtensions
{
public static IServiceCollection AddCoolFeature(this IServiceCollection services) => services
.AddTransient<IReducer<CoolFeatureState>, CoolFeatureInitializedReducer>()
.AddSlice(RootState.CoolFeatureLens);
}
Note that both generic parameters to AddSlice are cleanly picked up from
Lens<A, B> matching those parameters.
StoreI think the implementation of our central Redux Store follows pretty directly from our Slice implementation above:
public IActions
{
public IObservable<RootAction> Actions;
}
public IDispatch
{
public void Dispatch(RootAction action);
}
public sealed class Store
: ISlice<RootState>, IActions, IDispatch, IDisposable
{
private readonly IEnumerable<IReducer<RootState>> reducers;
private readonly Subject<RootAction> actions = new();
private readonly BehaviorSubject<RootState> states = new(RootState.Initial);
public IObservable<RootState> States => states;
public IObservable<RootAction> Actions => actions;
public Store(IEnumerable<IReducer<RootState>> reducers)
{
this.reducers = reducers;
foreach (var reducer in reducers)
{
if (reducer is ISubSlice<RootState> subslice)
{
subslice.RegisterParentSlice(this);
}
}
}
public void Dispatch(RootAction action)
{
var nextState = reducers.Aggregate(states.Value, (acc, reducer) =>
reducer.Handle(action, acc))
if (nextState != states.Value)
{
states.OnNext(nextState);
}
actions.OnNext(action);
}
public void IDispose()
{
GC.SuppressFinalize(this);
states.Dispose();
actions.Dispose();
}
}
public static class StoreExtensions
{
public static IServiceCollection AddStore(this IServiceCollection services) => services
.AddCoolFeature()
.AddSingleton<Store>()
.AddSingleton(provider => provider.GetRequiredService<Store>().States)
.AddSingleton<ISlice<RootState>>(provider => provider.GetRequiredService<Store>())
.AddSingleton(provider => provider.GetRequiredService<Store>().Actions)
.AddSingleton<IActions>(provider => provider.GetRequiredService<Store>())
.AddSingleton<IDispatch>(provider => provider.GetRequiredService<Store>());
}
IEpic<State>The final part to this pattern that does a surprising amount of heavy
lifting is the concept of an Epic. Named after "lengthy story", 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 async/await methods and running all sorts of side
effects.
Generally Epics observe Actions, the current state after an Action, and return Actions.
public interface IEpic<State>
{
public string Name { get; }
public IObservable<RootAction> Create(
IObservable<RootAction> actions,
IObservable<State> states,
IScheduler scheduler);
}
The IScheduler is explicitly provided to encourage testing with
TestScheduler and also because we found reasons at runtime to inject
specific schedulers without the Epic needing to be aware which.
The Name is useful for logging and debugging.
An example "Cool Feature" Epic might look something like:
public record CoolFeatureLoaded(IEnumerable<CoolThing> CoolThings)
: CoolFeatureAction;
public class CoolFeatureLoadedReducer : Reducer<CoolFeatureLoaded, CoolFeatureState>
{
public CoolFeatureState Reduce(
CoolFeatureLoaded action,
CoolFeatureState state
) =>
state with { CoolThings = action.CoolThings };
}
/// <summary>
/// When Cool Feature has initialized, load any applicable CoolThings.
/// </summary>
public class CoolThingLoader(ICoolThingService service)
: IEpic<CoolFeatureState>
{
public string Name => nameof(CoolThingLoader);
public IObservable<RootAction> Create(
IObservable<RootAction> actions,
IObservable<CoolFeatureState> states,
IScheduler scheduler
) =>
actions.OfType<CoolFeatureInitialized>()
// Sometimes a .WithLatestFrom() is useful here if the
// reducer calculates something, say in this case the
// API wants the calculated CoolStarted time. Actions are
// the "transaction model" for the Store, so the state
// after an Action is observed should always reflect all of
// the Reducers have run.
.Select(action => action.IsCool
? Observable.FromAsync(async (ct) =>
new CoolFeatureLoaded(
await service.LoadCoolThings(ct)),
scheduler)
: Observable.Return(new CoolFeatureLoaded([])))
.Switch();
}
// updated registration
public static class CoolFeatureExtensions
{
public static IServiceCollection AddCoolFeature(this IServiceCollection services) => services
.AddTransient<IReducer<CoolFeatureState>, CoolFeatureInitializedReducer>()
.AddTransient<IReducer<CoolFeatureState>, CoolFeatureLoadedReducer>()
.AddTransient<IEpic<CoolFeatureState>, CoolThingLoader>()
.AddSlice(RootState.CoolFeatureLens);
}
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.
The key wisdom when writing an Epic is that often attributed to Spider-Man's Uncle Ben:
With great power comes great responsibility.
Loops are a key flow control power in software development. Just as you
might use a while loop, you may find needs for Action loops in your
Epics. Just like working with a while 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.
EpicHost<State>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 EpicHost<State>.
public interface IEpicHost
{
public void Initialize();
public IEnumerable<string> EpicNames { get; }
}
public class EpicHost<State>(
ILogger<EpicHost<State>> logger,
IEnumerable<IEpic<State>> epics,
IAction action,
IDispatch dispatch,
ISlice<State> state)
: IEpicHost, IDisposable
{
private bool initialized;
private ISubscription? subscription;
private IScheduler scheduler = Scheduler.Default;
public IEnumerable<string> EpicNames => epics.Select(epic => epic.Name);
private IObservable<RootAction> CreateEpic(IEpic<State> epic)
{
logger.LogDebug("Epic {Epic} starting in {Host}", epic.Name, GetType().Name);
return epic.Create(action.Actions, state.States, scheduler)
.Catch(error =>
{
logger.LogError(error, "Error in epic {Epic}", epic.Name);
// Restart:
return CreateEpic(epic);
// NOTE: You might want to include a backoff strategy for
// restarts or other additional debugging tools here.
});
}
private IObservable<RootAction> CreateEpics() =>
Observable.Merge(epics.Select(CreateEpic));
public void Initialize()
{
if (initialized)
{
return;
}
initialized = true;
subscription = CreateEpics()
.Subscribe(
action => dispatch.Dispatch(action),
error => logger.LogError(error, "Error in {Host}", GetType().Name),
() => logger.LogError("{Host} completed", GetType().Name),
);
}
public void Dispose()
{
GC.SuppressFinalize(this);
subscription?.Dispose();
}
}
public static class EpicHostExtensions
{
public static IServiceCollection AddEpicHost<State>(
this IServiceCollection services
) => services
.AddSingleton<IEpicHost, EpicHost<State>>();
}
EpicNames can be handy for debugging and testing.
Somewhere in your application startup path where it makes the most sense:
var epicHosts = services.GetServices<IEpicHost>(); // or constructor injection
foreach (var host in epicHosts)
{
host.Initialize();
}
To finish up our Cool Feature example registration, it's final shape is:
public static class CoolFeatureExtensions
{
public static IServiceCollection AddCoolFeature(this IServiceCollection services) => services
.AddTransient<IReducer<CoolFeatureState>, CoolFeatureInitializedReducer>()
.AddTransient<IReducer<CoolFeatureState>, CoolFeatureLoadedReducer>()
.AddTransient<IEpic<CoolFeatureState>, CoolThingLoader>()
.AddSlice(RootState.CoolFeatureLens)
.AddEpicHost<CoolFeatureState>();
}
(You might want to inject an Epic Host for the RootState as well.)
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).
It needs this helper function for Reflection:
public Func<Type, bool> ImplementsGenericInterface(Type interface)
{
var genericInterface = interface.GetGenericTypeDefinition();
return (Type targetType) => targetType
.GetInterfaces()
.Any(x => x.IsGenericInterface && x.GetGenericTypeDefinition() == genericInterface
|| x.GetInterfaces().Where(i => i.IsGenericInterface).Select(i => i.GetGenericTypeDefinition()).Contains(genericInterface));
}
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):
[Fact]
public void ReducersAreRegistered()
{
var isReducer = ImplementsGenericInterface(typeof(IReducer<>));
var reducers = typeof(Store).Assembly.GetTypes()
.Where(t => !t.IsAbstract && t != typeof(Slice<>) && isReducer(t));
foreach (var reducer in reducers)
{
Assert.IsTrue(
Services.Any(desc => desc.ImplementationType == reducer),
$"Reducer {reducer.FullName} should be registered");
}
}
[Fact]
public void EpicsAreRegistered()
{
var isEpic = ImplementsGenericInterface(typeof(IEpic<>));
var epics = typeof(Store).Assembly.GetTypes()
.Where(t => !t.IsAbstract && isEpic(t));
foreach (var epic in epics)
{
Assert.IsTrue(
Services.Any(desc => desc.ImplementationType == epic),
$"Epic {epic.FullName} should be registered");
}
}
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 "glue" to connect all the small lego parts of the pattern together.
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).
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.
Given how much of the pattern seems to me to be most "DI glue" 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).
]]>The metric calendar addon is open source 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.
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 "candy bar" 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.
After many decades of failed or unfinished projects, I suppose that I'm officially now a "professional game developer" having received candy bar money for at least one effort. (That also amuses me about this.)
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.
Also, if you haven't yet tried Fantasy Grounds yet, I can recommend it as a nice VTT for many game systems. Just recently Fantasy Grounds became free-to-play (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 "offline-first" rather than "cloud-native". 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.
]]>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 "no moonlighting" 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.
I've taken technical liberties in how almost everything worked, but it's also been interesting after the major efforts were done seeking "Product Owner approval" 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.
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 "No Moonlighting". 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.
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 vibe coding. 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 "Weird Vibes (Software Engineering)", convergent evolution at work in describing the difference in a new workflow. (I was not aware of "vibe coding" at the time, but now it's such a common term.) My vibe was different from the one making the rounds as the titular "vibe coding". That January day I realized that my vibe was sort of distinctly "bougie" in a fun way. I realized that I'd spent most of that coding session feeling as if I was "leaned back", in a nice bath robe, a rocks glass of Scotch in hand, code reviewing the output of GitHub Copilot, much more than "leaned in" and directly writing code as I would normally be on a day job project.
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 "What if Masterpiece Theater was about Code Reviews?" I still kind of think that would be a cool company. I don't know how much of a market there would be for "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", but if you are an investor looking for that opportunity, hit me up, I've got ideas.
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 "basic CRUD" in the same way that this hobby project has been.
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.
The most common voting systems in our lives are all, in one way or another, "first-past-the-post" systems where a simple majority winner takes all. The very well known failure cases of those systems that are "first-past-the-post" are what leads to common modern problems like "two party systems" and ugly compromises like slate voting and "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".
There are lots of ways to solve this, but there's lots more ways that feel like they solve this but are really just "first-past-the-post-with-more-steps". Ranked choice (rank some or all the candidates from favorite to least favorite) often give the feeling of solving this easily, with relatively "easy" ways to assign "points" 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 "points" 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.
The nerdy mathematical name for this goal that head-to-head battles matter is the Condorcet winner criterion 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 "good" voting system from a "first-pass-the-post" one, there are several "competing" criteria that have different trade-offs. I like the Condorcet criterion best, because it allows for "surprising" winners but winners that most voters can still agree should have won, which is to say it is very good at avoiding "two party systems" where there's only two choices and the rest is "throwing your vote away" and "spoiling" a loser's chance.)
I became one of those nerds with a favorite voting system. That system is generally referred to as the Schulze method, or the "beatpath" 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 presented 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 "Round Robin tournament"), 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 "secretly" do all the work of doing a full pairwise ranking, with interesting ties, and have fun doing it.
Unfortunately the Schulze method is easier demonstrated than described, especially the math behind it. For a while there was a cool website called Modern Ballots, may it rest in peace, that I could point to do sample votes. It was rather close to a "Survey Monkey to make quick Schulze ballots". 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 Floyd-Warshall algorithm. 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 "Dynamic Programming" meant "solving a problem in place in its own existing data structure" rather than something more exciting than that.
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 "known algorithm" project after you think long and hard about it. The Floyd-Warshall algorithm is for finding the "widest path" 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 "how wide is this path" (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 "you should learn this because it is handy" turns out to be a real world thing sometimes.
The nickname for the Schulze method as the "beatpath" 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 "stars" 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.
One of the reasons that this hobby project turned into "bougie vibes coding" 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 "my Junior Developer", Copilot. Not all programming is "use this very well known algorithm in this rather well known use case" (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.
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 "silly fun hobby project". 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 used Schulze voting and Mozilla Persona. 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.)
Passkeys are the current hope for the present of "no passwords in my database". 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).
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 simplewebauthn 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 "DIY Here" 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 "Frameworks" 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.
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 "let Copilot write the first pass" and then clean up its assumptions and fix things specific to my backend and frontend choices. Not exactly the same "lean back" 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 "silly fun hobby project". Yet I succeeded in not storing anything even resembling a user password in the site's database.
Passkeys were one of my biggest anxieties in technical choices leading into the "MVP" 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 "oh yeah, no passwords".
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.
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.
The next biggest issue has been Windows 10 which is in a similar place of "supports Passkey" 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).
We also found out that the "the Facebook (embedded) browser" 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 "open in default browser" option strangely hard to find. (Today it's behind an ellipsis menu. Who knows where it will move tomorrow.)
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 only 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).
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.
Of course, some of this user acceptance feedback still feels like "early adopter" 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).
I wanted another public, open source portfolio project for Butterfloat. I'm still quite proud of Butterfloat and I know it isn't a "Framework" 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 ("traditional" for a static site generator [SSG]) "flat file database" of Markdown files in folders (with some modest YAML).
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 Lume as a minimalist SSG with all the basics covered that I expected and needed.
This seemed like the right project where I needed to finally test building out Web Components with Butterfloat in a classic multi-page architecture.
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 "modern Knockout" 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 "Progressive Enhancement" 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 "flash of unstyled content" is a strong improvement. Web component elements themselves have no default content or styles and if you place things like "noscript" 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. Stamps ("server-side rendering" 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.
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 documented the bones of the pattern already and hope it helps other projects looking for a lightweight alternative for a Web Component "framework".
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 can just do. 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.
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
class over className). 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.
My biggest pet peeve with building these web components is that the ESM native, properly tree-shakeable version of RxJS 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 "Signals" 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).
For this project the backend database I chose was Deno KV on Deno Deploy. I evaluated a lot of "serverless" 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 "fan bases". I started exploring Deno earlier when I made sure that Butterfloat got a good score on JSR 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.)
I'd pretty happily recommend Deno Deploy at this point. So far the developer experience has been great.
Deno's Deploy product on paper seems to have fewer features that most of the more popular/hyped options. It's primary database is "just" a "simple" key-value store without a lot of bells or whistles like some of the hosted SQL databases and/or "NoSQL" document databases that are "standards" in this area.
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 "young" Deno Deploy is and how many parts of it are still appropriately and visibly labeled "experimental" or "unstable", which shows maturity, to me at least, in how everything is documented. It's counter-intuitive but being able to clearly see the "experimental" and "unstable" labels helps my impression a lot. To some extent a lot of these "serverless" hosts still feel like most of their products should be labeled "experimental" or "unstable" and Deno admitting to it feels more mature to me.
As for Deno KV, I also knew from past experience that the border between "just" a key-value store and "a document database" is really blurry, especially if you don't mind doing a little bit of work up front on your "primary indexes" (your key building patterns) and rolling your own "secondary indexes" as needed. I especially loved that Deno KV brings its own invisible "path separator" for its key namespaces. This makes it easier to build smart "primary indexes" while also providing tools to avoid some of the obvious problems like key injection attacks.
There is a lot you can do with "just" 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.
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 "penny pinching" in interesting ways to help keep the site within Deno's current generous free tier.
In this case the Schulze method is very amenable to a classic "map/reduce" pattern, with each ballot being mapped to an adjacency matrix describing the "beats" 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.
The voting site backend is using Deno Queues 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 O(log n). I'm proud of
this bucketing, which is accomplished through a bit of maybe silly but
handy "primary index" magic of using for the ballot keys the reversed
string of the user's ID for the ballot. User IDs are ULIDs 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.
One of the things I’ve picked up from past SPAs that I’ve worked on is the importance for designing for "offline first". Some of the applications I worked on for past jobs demanded "offline first" 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.
But the thing I found from "offline first" is that it generally "feels better" and that it often feels like how modern apps seem that they are supposed to feel.
All of the CRUD (create, read, update, delete) work in the voting site is designed to be "offline first", using Local Storage generously and some very simple "three-way merge" techniques. (CRDTs are fun, but not what I thought was needed because ballots are intentionally single user.) This "offline first" approach may seem especially like overkill for a multi-page application because "the next page" 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 "save a draft until I come back" is fantastic if you've designed for "offline first", 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 "feel" 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.
(Between Butterfloat components caching well, the speedy loading of Stamp-based components, and the fast loading from local storage of "offline first" 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 "offline first" as a useful tool even for an MPA.)
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 "a team" for solo projects now. That also helps hobby work feel more like "senior-level" 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.
I've got a couple wishlist items out of this project:
I think there’s some useful Action Items for other projects coming out of this one:
[Mozilla Persona]:
]]>My guiding star for Butterfloat from the beginning was "Modern Knockout". 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.
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 "Progressive Enhancement" era.
I had made a promise to myself to include some "Progressive Enhancement" 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 "DNA" of the design. It just didn't make the "MVP" cut of the original release.
I'm excited that Butterfloat 1.5.0 fulfills that promise for the first time with Stamps. (It's an early "anniversary" gift, almost exactly a year after the first release.)
One of the prods that pushed me to think now was a great time to deliver on a promise of "Progressive Enhancement" 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 "modern frameworks" of today's React and Angular for users on cheap devices and bad connections. Because these sorts of frameworks have become the "default" or the "best practice" or the "easiest road" in so many places, they've become the tools in use even in places that should be prioritizing accessibility.
I don't think Butterfloat will single-handedly fix the anti-trend, but having tools for "progressive enhancement" was still important to me and the time is always "now" 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.
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 "Islands" to need to rewrite to. It's truly "progressive enhancement".
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
<template> and <slot> tags. They won't be rendered at all in modern
browsers until instantiated or unless "prestamped" into the container.
I keep referring to Stamps as just the "low level" building block for progressive enhancement/SSR/SSG. There are still more ideas to explore from them as a starting point:
<template> and <slot>, there may be good
scenarios to enable rendering them via the "Shadow DOM" rather than
binding them with Butterfloat.I don't know when I'll prioritize any of that work, given at the moment this is mostly a "free time" 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.
]]>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:
"type": "module" and "exports" in package.json"main", only "exports", but "exports" can be as simple as
"main" was"exports" understands "type": "module".js file extension.js file extensions for relative importsnode --test for unit testing"type": "module"The simplest thing to do when going ES Module only is to add a
"type": "module" to your package.json. That does most of the work.
It says all .js files are ES Modules. You mostly only need the .js
file extension for everything at that point, so long as everything is
modules and you want everything to be modules. You can forget entirely
you ever heard the file extension .mjs and you can mostly avoid ever
needing a .cjs file and that only for increasingly rare developer
dependencies.
"type": "module" works in all currently security supported Node
versions. It works for a few versions back out of security support,
too, now.
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 .cjs file extension.
Yes, you need to have the .js 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 "type": "module" 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.
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 .js
file extension in all your relative imports. (Yes, .js not .ts.
Typescript decided it was better not to transform this and to emit it
as-is, so it wants you to use .js 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 .js for you in its
refactoring/auto-complete added auto-imports once you do it enough.
My advice lately is that a reasonably good combo (per caniuse
statistics) for "module" and "target" in your tsconfig.json is
es${new Date().Year - 2}. I've been using "es2022" lately for both.
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 .cjs
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.
Jest still calls ES Module loading "experimental" 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 node --test. 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.) node --test 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 'node:assert/strict' and the describe/it test suite setup
functions from 'node:test'.
"exports": "./main.js""type": "module" does most of the work at runtime, but if you are
packaging you need "exports" in your package.json. Don't include
"main". (There are no bundlers that understand "exports" but not
"type": "module". There are bundlers that see "main" and
accidentally or intentionally ignore "type": "module".)
"exports" has a bad reputation for being complicated and confusing. It
can be as simple as "main" if you let it: "exports": "./index.js".
The requirements to use simple "exports" are basically everything this
guide is about: ES Modules only, with "type": "module", and one "front
door" for "bare" Node package imports.
The useful difference between "main" and "exports" is that
"front-door" only approach. With "main" Node would still let you
import from any .js file in the package and with "exports" you only
get the declared exports and nothing else, no more "sneaking in the
back-door". 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 "bare" imports).
Even if you do need more than one "front-door", you can still use a
simpler form of "exports" and ignore all the suggestions regarding
"import", "require", and "typings" subkeys:
{
"exports": {
".": "./index.js",
"./optional-cool-thing": "./cool.js"
}
}
Typescript since 4.7 has no problems picking up typings from packages
that include the TS sources or obviously named .d.ts files for any
"exports" configuration, including these simple ones, so long as files
are simply predictably side-by-side.
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.
You can use an importmap to import "bare" Node package names in the browser.
ES Modules work great in today's browsers with <script type="module">.
In a growing number of the simplest cases you can just prune and ship
node_modules to a web server, with an appropriate importmap.
npm prune --omit=dev will remove all of your development
dependencies. You may need to spot check for large files like test data
to also prune.
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. "Vendorizing" 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 --format esm flag, it's generally a simple one-line
CLI command that I can include in package.json "scripts" and/or
documentation.
ES Modules loaded as ES Modules is a great development experience in the browsers. You may not even miss "hot reloading" (and you can try to implement it without the big frameworks and bundlers if you still want it).
In HTTP/2+ and some well configured/behaved HTTP/1.1 servers there's a
lot less of a "per-file" performance hit than developers historically
worried about. Many ES Modules libraries are collections of lots of
small files, but the always asynchronous loading of
<script type="module"> combines with some optimizations in dependency
graph loading for smoother performance than many old pre-bundler
memories.
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.
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+.)
"sideEffects": falseIf 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 "sideEffects": false flag in your
package.json. 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 "type": "module" says you
know what you are doing with ES Modules and don't have CommonJS
transition code and synchronous requires.
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.
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 Butterfloat as one such useful open source library.
The Butterfloat Getting Started Guide 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 node_modules "bare"
package import of Butterfloat to setup a very light-weight development
environment.
In the ES Modules-only world of Node/npm/browsers/bundlers, everything
should start to be easy again. Use "type": "module" and you can
ignore a lot of old advice and/or outdated confusion.
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 "Angular Best Practices" as seen in an ecosystem full of blog posts and Stack Overflow answers available for copy and paste.
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 "Component Framework" to help build RxJS-focused components in a way that tried to lead to a "pit of success" 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.)
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 "good enough" for most projects.
In August of this year, after more than 8 years in that position, I was unceremoniously tagged in a "headcount reduction" due to a new incoming CTO and an expectation of a shift in software strategies.
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 "quick and dirty proof of concept". In dusting the cobwebs, dealing with the bitrot, paying down some of the tech debt of the CompRadProg 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 "elegant" some of my View Models have been in Knockout, including in this "quick and dirty proof of concept" demo that I love to talk about but don't really have much left to do with it.
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.)
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 "magic" of Knockout's "computed" 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.
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 "attributes" in TSX that were passed to the component.
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.
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 "secretly" themselves become Observables, too, at run time).
There are testing benefits to having this rich "intermediate description language" 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.
Cycle.js is a great Virtual DOM with Observables, if that is what someone is looking for. Butterfloat tries to be a "static DOM" with Observables in a way that I feel like I haven't really seen since Knockout.
One further divergence that I'm particularly proud of is that
@types/react 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) lib.dom types. I think I've got a better
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 lib.dom types include MDN direct links
in the documentation comments. I get those "for free" 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 @types/react itself.
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 "Float like a Butterfly, Sting Like a Bee". 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.
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.
]]>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 "damaged beyond repair", "sold at fundraising auction", and "on inter-library loan; overdue for return as of 03/1972". 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.
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.
"Dear Ms. Lynne,
"It came to my recent attention in discussing books with one Mr. Forsberg that he has a copy of J. Y. Milburn's Flight Into Night— A Slumbering Fantasia 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.
"Regards and thank you for patronage and the time out of your day to read this letter,
"Verity Hayward"
I had no trouble finding the card Mx. Hayward had attached to the letter. "X. Forsberg, Raconteur (Retired)" 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 "raconteur", of course, but something about that "(Retired)" seemed humanizing to me, and I wasn't sure why I felt that way.
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 "Truth" 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.
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, "Mr. Forsberg's private residence, who may I say is calling?"
"June Lynne, I received Mr. Forsberg's card from a librarian to discuss Milburn's 'Flight Into Night'?" I didn't mean for it to be a question, but it certainly came out as one in that moment between nerves and hesitancy.
The voice on the other end sounded excited and happy for me, "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?"
"I, uh, yeah I can make that work." I did not expect things to move so fast to a scheduled appointment, but had to admit I was free Saturday night.
"Excellent. I will let Mr. Forsberg know to you expect you here around then. Anything else I can help you with?"
"I'm not sure I know what the address is?"
There was a pause, then a hesitant, "I believe it should have been on the card?"
"Oh, well there's a street name but there doesn't seem to be a street number."
"Yes, that's the address."
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, "Alright, thank you," and we exchanged the usual phone conversation ending pleasantries and hung up.
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.
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).
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.
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, "Ms. Lynne, I presume?" I nodded as I took her hand into handshake and she continued, "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," her handshake was firm and commanding, "If you will follow me, I will lead you to the garden." She broke the handshake, unholstered the iPad and glanced at it, then proceeded to lead me around the house.
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.
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, "Please, call me Xavier if you would like," 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 "Ms. Lynne" in all cases of address. It started to give me the impression that "Raconteur (Retired)" 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.
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”.
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.
“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.”
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.)
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.”
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, "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."
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, "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 if 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?"
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.
"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."
I smiled, "I believe it's the hexagonal building I spotted from the street with the engravings of Athena watching its door?"
"Yes, of course! Very astute," 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.
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 "Use QR code for other catalog services, but please continue to use the card catalog as the main index. -V". 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.
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 "Flight Into Night— A Slumbering Fantasia" 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.
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.
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.
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.
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, "R. Forsberg, Jr". 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.
It took me several weeks of trips to the Forsberg Family Library to finish Milburn's "Flight Into Night". 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.
Milburn's "Flight Into Night" itself was equal parts amazing and infuriating.
The book contained some amazingly imaginative descriptions of a dreamy wonderland "just beyond the clouds, up the stairs, through the crystal tunnel, then across the dusk ocean, but only when you are asleep" named "The Far Kingdom". 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 "bland lands" "below" 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 Peter Pan or Wizard of Oz or Little Nemo in Slumberland 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.
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 "extremely of its time", 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 "exotic" 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).
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.
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 "Flight Into Night". 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 "it was a product of its time", the theory that some of its mean spiritedness and racism was so well read as to be intentional in its appropriation.
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, "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?"
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. "No, probably not. I mean it–" 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.
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, "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?" He offered his hand to me, and I accepted it.
"My father considered himself a failed painter," Xavier started as he lead the way in a direction I felt pretty familiar with, "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."
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.)
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, "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."
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 "Flight into Night" and the "R. Forsberg, Jr." signature on it, so I added, "And where books didn't have interesting colored covers, he painted some beautiful ones himself."
Xavier clapped in delight at my observation, "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."
He opened the door into the main hall of the library and ushered me in before continuing, "Of course, a lot of them needed new contexts, including Mr. Milburn's Flight Into Night. 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."
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 "R. Forsberg, Jr." 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 "trepanning" 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, "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."
I felt like I was still reeling at the given "random" example, "Why wouldn't you burn a book that awful rather than shelve it?"
Mr. Forsberg's eyebrow quirked up, "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?"
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 "Flight Into Night".
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, "Come, please. Allow me to show you my favorite secret in the library." 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.
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 "kerchunk" 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.
"Oh wow," 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 "trump l'oiel" 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.
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, "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.
"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." 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.
]]>Pharkas came from my frustrations with Angular, 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 "Project Gawky".
A few months later while debugging and fighting a Halloween sort of
ghost story in Angular caused by Angular's default change
detection system and its (over-)reliance on Zone.js, I felt pushed to
reevaluate the "Project Gawky" 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 OnPush change detection.
(At least from a "bottom up" direction, as default components can
use OnPush ones easily but vice versa isn't as possible or useful
in Angular.)
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 subscribe, and especially
attempting to eliminate calls to subscribe without a corresponding
unsubscribe cleanup at the right component life cycle point. The
benefits to following these best practices (as opposed to many
"best practices" Angular developers often are exposed to in the wild)
should always be a reduction of memory leaks.
Pharkas takes this further by automating the somewhat manual OnPush
change detection in Angular. Change detection is handled entirely
based on Observable wiring. Pharkas is designed to lead the component
developer into user-responsive components by default.
Pharkas' "DSL-like" 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 "verbosity" easily seems to disappear and Pharkas for became the only good way to write Angular components.
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.)
In reviewing the "Project Gawky" 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.
From the beginning Pharkas has made easy in Angular to do a lot of
the complex timing management that React calls "Concurrency":
any Observables bound to a template with Pharkas
this.bind(name, observable, default) will notify Angular's
change detector no faster than requestAnimationFrame time. This
keeps components highly user-responsive, as the browser will
throttle requestAnimationFrame time as necessary to focus on
user interactions and keep the UI responsive. (A browser may also
reduce requestAnimationFrame time when a tab is hidden or
backgrounded, further improving battery efficiency.)
(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
this.bindImmediate(name, observable, default) to force immediate
change detection on observations. In React this sort of thing is
needed especially for form elements when the "source of truth" 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.)
As I had surmised, this is easily accomplished by a relatively
simple RxJS operator and scheduler combo (it is about entirely
just debounce(0, requestAnimationFrameScheduler)) when you, like
Pharkas, are assuming entirely Observables (and nothing but
Observables), and are using Angular's OnPush change detection
strategy.
The other side of the React example over multiple major versions
was what React calls "Suspense". I had mentioned that among the
"Project Gawky" ideas, but hadn't implemented until now. In Version
6.0.0 Pharkas learned a very basic version of "Suspense": a component
may this.bindSuspense(suspenseObservable) 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.
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.
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 "intermediate" 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.
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.
It's also nearly as "simple" from an RxJS standpoint under the
Angular OnPush regimen in Pharkas: effectively just an extra
combineLatest and filter added into the change notification
pipeline. (It's also "pay to play" and if no suspense observable is
bound, these additional pipeline steps aren't added.)
It's not very discoverable, but you can see it in action in the Pharkas demo. If you click the test component with a counter the demo will now toggle suspense for that component. You may use the embedded RxJS-spy 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 "loading" suspended state.
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). Pharkas is MIT licensed open source on GitHub.
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.
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 "complete" 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.
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.
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.
]]>I'm going to lead with this: The key motivation for me was how important I think the Borg Coöperative are to Star Trek and why I loved the idea of late in life (ex-) Admiral Picard being the only diplomat who could "properly" 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 "that's not my canon" nitpicky way but missing some of the underlying raison d'etre and character motivations, including unthethering it from the "Prime" timeline altogether in a "we don't even trust it to be canon" sort of way).
It feels important to lead with motivation like that, because that's going to be a running theme in what follows.
(Also, I'm using the rare-to-English diaresis for the word "coöperative" in this article mostly because it is fun and I can pretend to be high society like The New Yorker, but also partly because it is fun how it can be confused to be Swedish enough to be a "Borg umlaut".)
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 "Alpha canon" are a useful shorthand for "Star Trek stuff that happened on the TV screen or a movie screen".
In Alpha canon prior to Star Trek: Picard there is one single, mostly standalone Star Trek: Voyager 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 Collective (we all know and "love"), decide to try to build their own culture with something more in lines with Federation ideals and as a "worker-led coöperative" where they could elect their own leaders and strive for a better Borg society.
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 "movement", it was just one mostly self-contained episode among many.
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 Star Trek Online 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 elected 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 allies (including in the fight against still deadly remnants of the Collective) and then eventually members.
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 quickly they assimilate other species, but the Collective is not the greatest assimilation force known to the Star Trek galaxy: the Federation is much 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.
The Federation frees what seems like only a few dozen Borg total, infects them with Federation ideals and virtues, sends most of them along on their merry way with seemingly zero after-care or oversight, and most of that was done by a single ship lost 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 talking to people.
If you've been following along in Alpha canon, that irony of the Borg Coöperative being an accident of infection of Federation ideals shouldn't be a surprise. The Alpha canon is full of Federation "exceptionalism" 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.
Beta canon followed that to some of its logical extremes: that picture of Borg Coöperative members serving in Starfleet as another "species" adding diversity of thought and culture to the Federation.
I personally love that. That's my Star Trek.
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 Star Trek: Picard.
I think it is clear at this point that Star Trek: Picard put the Borg Coöperative onto the tee, went to swing at it, and entirely whiffed it.
It's implied but never actually text in any season of Star Trek: Picard 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.
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 internalized quite so much of Federation ideals.
Though admittedly a lot of that is still implication and supposition because there is not enough text 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 "twist" that it was Agnes then didn't really have time to tell 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.
That leads into what I think were the largest problems of Star Trek: Picard: "No tell, only show" and "twists/late infodumps over exposition and character motivation". Neither of these problems originate with nor are unique to Star Trek: Picard. One is just kind of an accidental product of post-Lost media. Everyone knows "People love twists!" 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 might be relevant later, maybe, if the "twist" is right and it wasn't a red herring.
The "No tell, only show" seems like the obvious pendulum extreme affecting almost all of current "prestige television" and precisely what you'd get from a generation and a half of writers drilled on "Show, don't tell". "Show, don't tell" comes from a good intention. It's meant to spark creativity. But it's a pendulum and both extremes are painful. Too much "tell" 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 "show" and not enough "tell" 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 "oh that's what that was about". (Which is easily confused for "twists" and exacerbates and feeds the other problem.)
To me Star Trek: Picard is one of the most emblematic shows in all of television for "No tell, only show" especially because we have the context of Star Trek: The Next Generation (and TOS, Voyager, Enterprise): so many TNG episodes are an amazing balance of tell and show. So much of Star Trek storytelling is dropping the right "Captain's Log" at a good moment to quickly push the audience on to the next thing they need to know.
The combination of these problems leads me to this feeling like most of the story of Star Trek: Picard was told "backwards and upside down".
I have this impression that you could especially take the first season, "simply" reorder it, and tell the same story better. That if you focused on getting more motivations up front, rather than saving things for "twists" 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, was to set up seeds for the Borg Coöperative.
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 maybe a choice "Former Admiral's Log" voice over here or there.
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):
Dropping in a quick aside here, to break the bullet point rhythm, it's still an incredible shame that given the events of Star Trek: Nemesis that not a single named Reman existed in Star Trek: Picard. 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 Reman Unificationist group. "The Way of Absolute Candor" versus the culture of secrecy and lies of Romulus prior to the Hobus explosion seems almost obviously 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/"Buddhist reformation" of Surak's teachings on Vulcan, and "absolute candor" 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.
Back to the story reorder in progress:
In that order, even as just bullet points, I feel like that’s a pretty good plot. Certainly better than what Season 1 of Star Trek: Picard seemed to show us, right?
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 "next season". 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 "text").
I think that there's only a couple things that I would have loved to see or at least have told in that season, rather than leave things to supposition.
I like to highlight the Federation ideals here. Don't murder the villains, let them face justice and account for their actions.
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.
Beta canon suggests it could 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.
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 "Picard Effect".
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 "never followed up on". Part of the fun of something like the Beta canon is all the fan hype about "This time it is the Iconians for sure!" but Alpha canon has never mentioned them again outside of a throwaway gag in Voyager and a throwaway mention in Discovery (centuries after TNG).
It's great to have these bits of unexplained whatever. "Give us more tell" also doesn't mean "tell us everything", it's still a pendulum. Briefly show and leave unexplained works well when intentional.
Star Trek: Picard 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 Star Trek: Picard 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 is the measure of a man?) (At least until that Romulan spy sabotage thing, whoops.)
The right thing to do would be to welcome the Soongs back into the Federation after their androids (and "synthetics" derived from same) had done so much for the Federation for so many years. It's cool and all to see "yet another" 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 "chess ending" or "poker hand".
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.
I think some of these were good bones in S2:
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 a 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 "President" of a Coöperative. The kind of person that could stand up and say "No more Queens!" and mean it.
(Agnes doesn't really make sense at all in S2.)
There is a fun symmetry to be had in using Star Trek: First Contact 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.)
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).
There too, motivation would add good drama. 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. But if the audience has a good idea that the Coöperative is who they say they are, up front, that's good dramatic tension, not just "he's probably right, no one should trust the Borg" which S2 suggests right until the last minute "twist". 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).
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 electing 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 "worker's coöperative" should come from the same place it is in rebellion against and there is such juicy fun in the idea that Voyager, of all ships, with no other help, accidentally seeded an entire faction of "good Borg" in the home turf of the Borg while just trying to get home.
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 great idea and it was so poorly executed most people have no idea at all, whatsoever, what "Agnes' Borg" 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.)
]]>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.
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 ("down the street" 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).
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 Puzzle Pirates as a particular influence at that time on ideas you could accomplish in a "low fi, bootstrapped" 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.
So in all that I think I had a rather deep education of the state of MMO research and the "classic" 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).
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.
Games are incredibly important to the (Australian originated) children's television show Bluey, 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) [1], 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).
Almost everything in Bluey 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, "Hooray!"), 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.
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 "rehearse" 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 Bluey throughout and some of the most fun episodes leave you wondering how much "magic" was real life. You get the sense over three seasons that Bandit, especially, Bluey's dad, will do anything 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 "for real life" (including, among other things, the friends you make along the way and the family you keep).
While watching, I continuously had that weird feeling that so many episodes of Bluey 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 "lecturing on it" turned into "writing a blog post" (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 Bluey. 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 Bluey 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.)
In November's Season 8, Sea of Thieves 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 "New Arena", 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 "the rest of the game" in a better way than the "separate but not quite equal" 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).
My harshest take has been that the new system is "Emissary 2: Emissary for Masochists". The new system introduced two new Emissary factions that were clones of previous trading company/emissary factions but "PvP Only now". It took some existing concepts such as the Emissary flags and their relationships with factions and built new slightly different versions but "PvP Mostly now". 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 "PvP Only" 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).
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 "MBTI or horoscopes for MMO players". At its best, however, there's still some usefulness in the way that Bartle types can describe a "four quadrant" 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.
I think my greatest criticism (as compared to my harshest take) about the new PvP system is not just that it is a "one quadrant only" solution, but that it feels a lot like a "less than one quadrant" system as it is currently balanced and based on that self-described demographics "rule of thumb" 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 is 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 "three quadrants".
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 negative emotions out of other players. (Bluey 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.)
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 "raw" 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 "interested in PvP" is always going to be the more "lucrative" 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.
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 "numbers go big" metric for them to watch and brag about like many other common Killer-focused games such as the Call of Duty franchises, Rocket League, Fortnite, 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 Sea of Thieves 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 Sea of Thieves tries to feel like an even playground/sandbox so such numbers are also somewhat counter to that spirit of play.
I know the developers of Sea of Thieves tried to mitigate this somewhat with the "Defensive" option to play the new systems, where it is most and worst like "Emissary 2: Glowing Figurehead Boogaloo". 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.
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 "sometimes PvP" 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.
In contrast, the new "only PvP" factions and the new "Emissary 2.0: Hurt Me, Please" 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 "invaders" that can spring out of the water at any time. ("Invading" is of course the other option from "defending".) 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 "Invading" 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.
Anecdotally, from doing a lot of grinding of "Invading" very few seem like players actively defending a loot haul and the vast majority seem like "fellow invaders". 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 "Invading" cycle, further making it the key focus of the new system and further leaving me questioning who the "Defending" mode is for given its risk/reward balance.)
My "home" 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 "socializing" than "killing".) 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 Sea of Thieves statistics but my own.)
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 "not fun" 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.
As an Explorer, I've explored how repeatably badly I can intentionally suck 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
do x, y; wait until z; do a, b; wait until c; repeat. 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 again 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, "Legacy" 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 "nearly completed" 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).
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.)
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. (Bluey has an episode or three about that.)
Folding Ideas somewhat recently dropped an hour and a half dive into the Raid-focused and end game Metrics-focused culture in the MMO World of Warcraft called "Why it is Rude to Suck at Warcraft". This dropped late in my Bluey binge and its Imaginary PhD dissertation and with the giant swirl of Sea of Thieves 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 Bluey episode.
I'm not that wide or deep in my "pure" 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 "game engine" 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 "follower" 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.
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 "text book" 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 "long out of print" (there are hardcovers for sale for as little as $6 used on Amazon).
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 spoiler warning 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.
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 Bluey 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.
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 Bluey 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. Bluey 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 World of Warcraft 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 "What do we do about it?" It speaks directly to that conclusion the video thinks it makes: if games are people, the rules we build to "play nice" and try to get children to learn from an early age, apply at every age. To some extent it really doesn't matter if you are playing "Keepy Uppy" or World of Warcraft if you can't play nice maybe you shouldn't play at all. Those are eternal lessons to learn about human society, which why we work so hard to teach them to children.
As nearly a tangent here from "Imaginary PhD dissertation" brain: one of things I found so powerful about Bluey 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 for real life. 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 "safer spaces", they are "safe spaces" only somewhat 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. (Bluey has many of its share of those same sorts of games that children play to learn society itself: "Doctor", "Shopkeeper", "Parents", and so many more. Again, with Bluey'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 "well done", but I can suggest that at least one fever-addled, childless adult was so impressed they imagined they wrote a PhD dissertation on it.)
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 Bluey 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 "safer spaces" and "safe spaces" and a second-hand criticism that the early literature focused possibly too much on "safer spaces". A lot of the literature did explore the concept of "ludic spaces" as "safer spaces" and what that meant about games, but most of it was specifically coming from that "we think of games as childish or child-like" and directly defining how "ludic spaces" may seem "safer" at first glance from a raw risk/reward standpoint than "real life", but absolutely exploring all the ways that "ludic spaces" are always still a part of real life and "safer" is never safe from real world consequences nor real world harm.
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 Second Life and deeply proving that any attempt at "second life" is just "real life". 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 Second Life'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 EverQuest 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 Sims Online. Sims Online 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 Star Wars: The Old Republic. (A lot of history rewriters today claim that EA got out of MMOs because they couldn't compete with early World of Warcraft, but that narrative forgets key parts of history that we shouldn't forget. This isn't even a "distant past" problem, even just recently a parent sued the videogame Roblox among other defendants for its part in facilitating dangerous real world trafficking.)
The other reason that the Folding Ideas video gives for why they stopped their research too short is "World of Warcraft was the first game to…" This is patently untrue for many reasons, including starting from their own conclusions. World of Warcraft 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 "some people in WoW were mean to me because I wasn't using endgame-class/raid-level analytics tools and tactics" feels utterly banal. It feels almost as childish as those same WoW players would likely dismiss Bluey for being too childish and inapplicable to their circumstances, despite being almost the very sort of problem Bluey was built to teach about. Even extending that "first game" through the video's various qualifications and hedges: WoW wasn't the first MMO with deep scripting. Second Life as mentioned had user scripting crimes even. Nor was it the first RPG game-like MMO with deep scripting. EVE Online 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 "other side" of the hill that graphical MMOs still haven't quite hit (though ones like Second Life 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 "perfect tactics" 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 "Foundry" efforts and tools in their various MMOs over the years as well).
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 "followed the links" to earlier works that would have enriched 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 "all four quadrants". 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 cultures 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.
I also have few illusions that I've done much better than Folding Ideas at beating a random Bluey 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 Bluey. 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.
Not to be confused with Game Theory (of Economics), though there is some obvious cross-over. ↩︎
A Discord-posted meme of a tweet of how hard Mark Hamill looks in Slipstream, a 1989 rarely seen film, reminded me that the movie had been sitting in my "To Watch" pile for some time. I did not recall why it was on the "To Watch" pile, it might have been because it was a "lost classic", it might have been as a "so bad it is good 'classic'", given the age of how long it had been on my "To Watch" 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.
The reminder that it was on my "To Watch" 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.
The was directed by Steven Lisberger best known for Tron, 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 Slipstream budget was coming from. Oops.
I thought Slipstream 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 "lost classic" 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.
The closest comparison for several reasons seems to be Waterworld, despite Waterworld having been produced after Slipstream. Slipstream plays as something of an "Airpunk" Waterworld (an_ Airworld_? [1]). It was equally a flop like Waterworld for budgetary reasons, but where Waterworld simply spent too much, Slipstream seems cheaper and overall probably well budgeted if the personal mistakes of the publisher hadn't interfered. Waterworld has a seriousness to it that plays corny, whereas Slipstream 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 Star Wars and Indiana Jones mine those old tropes.)
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 Mad Max-style "post-apocalyptic road trip adventure", with the interesting twist that all the "cars" in Slipstream 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 "drop" tracks, amusingly diegetic in those moments and some equally harder than they needed to be tracks.
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 Matrix sequel, but work well in the context of the movie.) The primary protagonist of the film is pre-Twister 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-Jurassic Park Ben Peck (as in "Clever Girl" 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.
Strange caveats to mention:
Other than that it was a fun "so (intentionally) bad it is good" movie with an interesting "lost classic" history and vibe to it.
Does that imply the eventual existence of an Earthworld and a Fireworld? No one will expect it when the Fireworld movie drops. ↩︎
The short story is that I've built a growing collection of libraries around what I've called the Pharkas Component Framework. It codifies a lot my "Observables-only" best practices into what I hope is a "pit of success" tool that's easy to slot into existing ("brownfield") 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 "grease the wheels of".
I thought I would narrate some of the longer story as well. I'm very proud of Pharkas 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.
When I last blogged about Angular I was obviously already trying to think of constructive ways to build my way out of the mess. I mentioned these "Project Gawky" 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 "it just uses HTML" and as you would imagine this means that Angular's (massive) template compilers (there have been several massive rewrites) themselves are somewhat "secretive" 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 "Project Gawky" was much more of a pie-in-the-sky rewrite idea than a pragmatic solution to offer.
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 "stall out" for minutes of wall clock time. There was no noticeable network activity, no useful "progress" indication, terrible responsiveness to user interactions ("slow"/"ignored" clicks), and not even a "please wait/processing" beach ball or spinning hour glass: it was just the absolute possible worst user experience and it was making our production users angry.
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 "navigate the app randomly for long enough".) 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.
I started with a lot of taps and console.logging debugging, and in the
middle of that was pointed to RxJS-Spy 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 console.logging and debugger
breakpoints. Again, it's just a great improvement on "tap-style"
debugging. Install it today. (I have nothing to do with RxJS-Spy, I just
keep recommending it to projects now.)
The more I tagged with RxJS-Spy 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.
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?
In just about any other framework I would have already have pulled out
"flame graphs" 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
console.logs 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: Zone.js.
Zone.js is a supposed "prollyfill" 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 "customer" of Zone.js, and today is entirely embedded in the Angular repo. Angular uses Zone.js deeply 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.
This "infection" 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. You may insert here in your mind an "Everything is fine" meme with the dog labeled Angular and all the flames labeled Zone.js.
Somewhere in those Zone.js flames your code is probably running. Somewhere.
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.
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 single 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.)
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.
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! 👻
This absolutely is one of my deepest nightmares as a sometimes "performance expert": 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.
Hyperbolically, I went insane here. I lost my damn mind.
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 "just do my job" and patch a fix.
That left to me the only "logical" and "pragmatic" realization: Zone.js Must Die.
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.
So I started researching everything I could to murder Zone.js without breaking Angular.
Angular is kind enough to give you the option to boot up with a noop "Zone" 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 "Zone".
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 "push": they push notifications when changes happen. You shouldn't need change detection in a pure Observable world, you already have change notification ("for free"), because that is what Observables are. (This is where the "Project Gawky" idea gets most of its promise: with "free" change notifications you can wire it to do some very smart things also "for free".) But Observables are "hard" and Angular couldn't commit to them and the resulting worst of both worlds compromise "needs" Change Detection.
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 "hey, something changed somewhere, I don't know, maybe" (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.
Fortunately Angular seems to have actually anticipated this, too, that with Observables you have a "push" 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 "manual stick shift" option for writing components: you can annotate in the Component decorator that the component uses the Change Detection Strategy named "OnPush" and that you will push all change notifications manually.
The "OnPush" 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: OnPush components do not need
to be wrapped in Zones (and generally aren't, though Zone.js is
"viral" in nature and there are no guarantees it doesn't accidentally infect), which is great. But
that also means that OnPush components can only ever use other OnPush
components. Components that use the "Default" change detection
strategy and need Zone.js to detect their changes can use OnPush
components just fine, but not the other way around.
But a "bottom up only" hope in a brownfield application is still a ton of hope to make a noticeable change. A "manual stick shift" 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 "pragmatic" and it will get the job done.
To recap: I lost my mind in horror. I decided that Zone.js must die. Then I finally discovered some hope for a "bottom-up solution".
I realized that I could build it: I could codify my "Observables only" way of building components into a library, and use that library to build a handy "automatic transmission" to replace Zone.js-based Change Detection with something smarter and less compromised (if it sticks to "Observables only").
Unlike "Project Gawky", I had a firm place to start to build a useful,
reusable library for building (Zone-free) OnPush 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 "smarts"
ideas of "Project Gawky" and apply them as good defaults. By making
them good defaults I hope that my library can build not just a "pit of
success" but a "pit of smart success" 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 "concurrent
mode" which deprioritizes most DOM work until after idle callbacks such as
requestAnimationFrame 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 requestAnimationFrame 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 ngOnInit and ngOnDestroy lifecycle callbacks.)
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 "marble diagram" timing tests added. Because of that
useful default of debouncing to requestAnimationFrame, I need a marble
diagram harness that understands and fakes requestAnimationFrame
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.
I named this library "Angular Pharkas" and the approach the "Pharkas Component Framework". 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, and 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 "professional" approach, but sometimes we need humor in our darkest hours.
To entirely over explain the joke: Freddy Pharkas: Frontier Pharmacist was a 1993 adventure game from Sierra On-Line near the peak of their development golden age. It can be described as the "Blazing Saddles of videogames" 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 Blazing Saddles 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 Blazing Saddles.) 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 "Rx" in addition to technically meaning "Reactive Extensions", which was the original .NET name for its Observables-pattern framework, is also one of the more common abbreviations for the word "prescription" sometimes stylized ℞ and has been used by pharmacists for that word for a long time, from latin "recipe" meaning "take".)
Beyond the base component and the library that provides the core "Pharkas Component Framework", 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.
So far the biggest running theme of these other components is
providing Angular wrappers for "Vanilla JS" 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 "business critical/secret sauce/non-disclosable" 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 "bottom up" approach to converting to OnPush
components especially highlights your "VanillaJS wrapper components"
as a key "bottom" that needs conversion early.
I think "Vanilla JS" components (and components from outside
frameworks embedded inside Angular) are especially ripe to gain the
benefits of OnPush style components: they shouldn't have any
change detection needs because they handle everything internally.
Wiring all of a "Vanilla JS" 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 tries not to be that
"viral", 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 NgZone.run callback. (Default components using OnPush
components don't need NgZone.run callbacks in my experience, that
boundary is handled automatically enough, unlike the "Vanilla JS"
boundary.)
I think these Pharkas "family" of "Vanilla JS" wrappers should serve
as useful examples of the gains to be made in using OnPush 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 NgZone injections and no NgZone.run calls. There's no
change detection notifications necessary at all when the component
handles all of its own update cycles.
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 "escape hatches" 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.
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.
The "demo site" for Pharkas right now is a collection of these "Vanilla JS" components themselves (more than one!) used in a combined "dashboard" with (fake) real time data. I'm incredibly biased here, but I have never seen performance that strong at the "Vanilla JS boundaries" anywhere else in the Angular world. The real time is "fake" 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 "Zone" so truly has no Zone.js at all even in accidental fallback.)
All of this is MIT licensed open source, 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.
The "Pharkas Component Framework" is agnostic to how you build Observables, it only mandates that you use Observables.
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.
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 "without them" approach I currently call "lots of small Observables" which may be possibly called "Atomic Observables" in analogy to the React "counter-Redux" term "Atomics". Today I don't have a library to offer on this pattern. I see it as simply a pattern and an "obvious one" at that, so I don't think it needs a library at all. (I might even describe it today as a "natural" pattern of Observable building, implying it is the NgRx/NgXs/et al of the world that is perhaps a bit "unnatural".) 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.
At one point I thought Pharkas "needed" a state management answer or to be a little bit less state management "agnostic" to be a "real" 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.
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.
Highlighted and self-aware summary of previous sections:
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.
What I would like? Please learn from it! It's a handy, easy to explore
MIT-licensed Open Source repository. If there's one particular
takeaway here: use OnPush components everywhere you can! This truly is
a bottom up initiative. It needs to be "grass roots" in Angular, because it is not the default. Component
developers (especially those wrapping "Vanilla JS" components) should
start leaning OnPush 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 "haunted house framework" or worse, but after having experienced
that every library I see in Angular I now evaluate on "does it use
OnPush components?". You don't need Pharkas to build OnPush
components. I think Pharkas makes it very easy to do that, and adds
some nice "automatic transmission" 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 OnPush components, I don't care
how you get there ("manual stick-shift" or not).
I hope I have set a good example here.
Happy Halloween, and good luck if you are an Angular developer. 🎃
]]>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.
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 "homework".
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.
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.
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.
"Don't Grow Old." 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.
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.
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.
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.
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.
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 "don't grow old," 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.
It's a deadpan joke where the punchline is terrible. "Don't grow old." 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 "Never Grow Up", 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.
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, "don't grow old." Hah.
]]>Technically, it is only the 19th anniversary at the domain, because of how anniversaries work, but the off-by-one big round number of 20 has still caught me somewhat feeling reflective of the passage of such time anyway. While it is an anniversary I mostly just celebrate with nerdy bits of CSS and HTML, it's still so tied into different eras of my life. While some of that history is lost in the current archives, for better and worse, there are still lines I can see in this blog's history. For examples: there's the point where I stopped publishing political opinions because I got tired of fighting over them, and there's the point where I stopped posting so many short posts because Twitter was built, and those two lines nearly but don't entirely intersect.
Most of my Twitter posting history is in a zip file at this point as I felt that I had to delete it from Twitter itself. Sometimes I wonder about backfilling short posts and "best of archives" into the blog. I don't know what is in that zip file that remains relevant out of context. That's part of why I deleted it off Twitter, because time robbed so much of it of context and robbed of context they were liabilities I could no longer as easily control. I was a bystander in such an attack on old out of context posts, and saw enough to leave. Adding them to the blog could give them new context, but I'd have to curate them first.
Similarly, I know where the line is where I removed direct commenting on my blog. Comments were something that I felt important so early in the blog. It was feedback and community. Of course, back in those days before Twitter and Facebook and "social media" that was what blogs were for. The last comment platform I used was Disqus and I dropped them between privacy concerns with how they were treating data and a desire to reduce JS on my blog to a bare, optional minimum. (Not because I have anything against JS, it's often a huge part of my day job, but because it sometimes gets in the way on a blog in performance and flow of reading.) I've got a dump of all the comments in a zip file, too. Some of those I recall were good discussions and I think sometimes I should post an archive of at least the better ones, back in context of the posts they referred to. Other times I wonder if it is better that those comments are gone, the slates clean. It feels a bit like the same sort of for better and worse forgetfulness that wiped several years of college posts from the blog. Some of them were interesting time capsules and others were worth losing for the pain and/or problems they caused alone.
I can still see so many lines that marked ambitions damaged and/or major project failures. I joke these days that "WorldMaker" as much as anything today is the name of my impostor syndrome. It comes up as a "joke" more often than I'd like because I still use the handle as my primary in gaming and on Discord, out of decades of momentum at this point. I still mostly remember why I picked it, so many years back in high school on IRC and in forums. In some ways it is weird to still be using it most of a lifetime later.
I've never quite been as prolific at short fiction as I tried to be in high school. (The loss of a few short stories is what I mostly regret in the holes in the blog archives.) I've only posted a couple of short stories in the past few years. On the other hand I may have been getting better at longer forms of fiction writing. I've won NaNoWriMo twice in the last few years. One novella was a game of pronouns and multiple narrators that test readers didn't like (and I couldn't blame them; it was an intentionally experimental game), and the other is a work of fan fic over on AO3 for now. It's been too long since the last attempted game I finished writing, and I've got experiments in the forge, but no idea if they'll ever be more than just experiments.
The basics of LCARS Moderne are largely the same: at typical tablet widths and wider it is a Star Trek inspired library interface with a curved C-shape around the main content area, which still scrolls horizontally by default (with JS enabled; more on this in a bit).
Most obviously, I refreshed the colors, now with palettes that are inspired by the color choices of Star Trek: Lower Decks. On the technical side I moved the palettes from build time SASS variables to run time CSS variables. Dynamic colors were features I put too much work into previous blog designs, so it's something I appreciate about this upgrade (and how much easier it is today, just swap a CSS class). I tried to be subtle with it, and some pages now use different palettes based sort of on their function/context. It's not quite the every page load uses a random color scheme of some of my (lost) past designs (back when that involved so many image files to accomplish).
For a fun example of the more dynamic nature of the color system, a red alert toggle button:
I revised a lot under the hood, in part based on six years of feedback, and in part based on changes in how browsers displayed the site over six years.
So the big thing is that by default I still wanted it to be a multi-column layout like a newspaper that scrolls horizontally on any screen wider than narrow (bigger than a phone screen). I know it is weird, and I've seen a bunch of hateful comments over it, but it's a retro-future weird that I like and this is a personal site that's allowed to be personally weird. I've got an ultrawide monitor on my desktop these days and I like that I have just about the only site on the web that I can full screen on my screen and the site naturally makes good use of all that width. I can read entire articles sometimes without scrolling, just reading it newspaper style from column to column. I still think that is neat.
I did try to address some of the "affordance" issues that had been pointed out over the last few years. (Affordances are things that make it more obvious and/or easier to work with.)
Six years ago it used to be that multi-column layouts, when in a container that scrolls horizontally took the column width you specified as-is and ignored the view container size, so a non-percentage width would cause columns to be cut-off/bleed-through the edge naturally. I thought that was a useful affordance for "there's more to scroll" if in most default browser sizes there was always a bit of column peeking out across the edge to encourage scrolling. Since then the browsers have all converged on a behavior where the requested column width is now just a minimum size and even when the container scrolls horizontally it still tries to balance columns so that they fill the viewable area of the container "perfectly", removing that hint of a column just slightly cut off or just slightly peeking in that there's more to scroll. I'm not a fan of this newer behavior the browsers converged to, but I understand why they did that: the new behavior is what more people want, because fewer people actually want horizontal scrolling and they'd rather things look more "perfect".
I tried to add an affordance back here by adding a "column rule" between the columns. I still don't want a column rule, as I don't think it matches the aesthetic I'm going for, especially with how little options there are for how a column rule looks. It is limited to an early subset of CSS border styles that feels like a throwback to me. I wanted to pick something unobtrusive that kind of blended into the background, but I also wanted to pick something that maximized the "affordance" capabilities of the column rule here, as its one of the few remaining obvious hints that "there is more to scroll". The rules only get drawn in between columns, so if you see one at an edge at all, it does imply that there is more to scroll. But I was worried that might be too subtle on its own, so I increased the size of the rule and chose a pattern that is more obviously "cut in half" on the edges. I played with the sizes until I reached a point where I thought that effect was obvious but not distracting. I'm still not entirely happy with the column rule, but I think I've done about the best I can with the current CSS tools.
Six years ago there was a non-standard CSS flag I could use to ask the browser to let the scroll wheel scroll the page horizontally. Because it wasn't a standard and only supported by a couple of browsers it eventually stopped working. So I've added a tiny bit of JS that takes care of it now. When the container is scrolling horizontally the JS code treats scroll wheel scrolling from your mouse as if it was horizontal direction scrolling rather than vertical. This does drastically make it nicer to scroll articles again (if you aren't full screen on an ultrawide or are on one of the longer articles).
While I was touching it, I also added a CSS class to toggle back and forth between the horizontal scroll and vertical scroll. I also added a not-subtle-at-all button with JS to toggle that and also point out the current scroll direction. It's a lot less subtle than the old unaligned columns or the new kind of subtle column rule. It might be subtle that it also acts as a toggle button, if someone really, really wants it to scroll vertically "like normal". I don't recommend that as the best experience on most browser sizes. I still have text justified at all sizes, which I think looks great in multi-column and phone, but won't look as good at larger window sizes scrolling vertically. I'm also making the choice to not cap the width when it scrolls vertically. So many websites do that today. It's often seen as "necessary" to keep people's increasingly wide screens from making text regions too wide to be easily readable. My preferred solution to that is multi-column layout and horizontal scrolling, as clearly mentioned here, so if someone chooses to scroll my site vertically, I'm going to leave it to them to manage the width of their browser without me making a strong CSS statement on that.
Both of those JS features, the scroll wheel translation and the toggle button seemed usefully necessary affordances that at this point it is the JS itself that "activates" the default horizontal scrolling, so if you don't have JS enabled you get the vertical scrolling. I still think of this increased amount of JS as "optional" in itself, but given the horizontal scrolling now "requires" it, it's certainly again the case that the best experience of my website is with JS enabled. Few people disable it to notice these days (as opposed to my over-indulgences in JS in my youth), but it's a consideration I made all the same.
On that front, I did a bit of "CDN elimination" moving away from third party hosts for code, images, and fonts and moving things back to hosted locally only on the site. That should eliminate all third party trackers on this site now. (I don't have any first party trackers as I stopped caring years ago, and I would assume GitHub likely has "second party" trackers in its GitHub Pages hosting.) I moved to a bundling and install process for LCARS Moderne 2.0 updates, which is kind of neat behind the scenes trivia. (esbuild and npm with a bit of robocopy if you are curious.)
I'm proud of this blog theme and I suppose I expect it to last another six years at least. I expect I'll hear plenty more feedback on it still.
]]>This novella, A Visit to the Sea of Thieves, is my second NaNoWriMo "win" across many attempts. The first (final working title was The Space Train on the Way to the McMaster Heist) was an interesting game of dual unreliable narrators experiencing the same events (planning a heist while living as the hospitality crew on a space train) from different life perspectives and also a weirder game of pronouns (most of the crew used they/them, characters change pronouns midway through, some pronouns were chosen to intentionally be clashing beside traditional familial relationships). Those games kept it interesting to write, I felt like they were in service to the story, and helped contribute to making it a "win" in NaNoWriMo, but the beta readers I got (great) feedback from didn't like it very much and I entirely understand why that would be. So far my best idea for reworking it to be more readable involves adding a third, maybe more reliable, narrator and while that makes sense in my head, I think on paper it would just add more confusion.
A Visit to the Sea of Thieves also did not impress some of the beta readers I tested it with, because it a work of fan fiction, and because there is a stylistic choice I felt necessary to make, but I will get to that. I posted it to AO3 in case maybe someone has nearly as much fun reading it as I had writing it, but I'm not sure who that audience is beyond just myself.
I've been a regular player of Sea of Thieves since its launch. There are multiple blog posts in my archives here where I go on at length about Sea of Thieves topics. You can also see the threads of why Sea of Thieves would appeal so much to me in even earlier (and still discoverable) posts about games like Puzzle Pirates. I love the pirate theme. I love the cooperation aspects of crewing a ship together with other people.
I've developed a lot of weird skills across a lot of regular play. I may not be the best contributor to PvP on a ship, but I can helm and sail (solo if need be) any ship in the game. (Sometimes that need is strong: the rest of the crew is busy and you need a ship from one place to another in a hurry.) At one particularly weird point in my play I got absurdly good at dual controller play while soloing a ship. There aren't a lot of good reasons to need to multibox in Sea of Thieves, it was mostly for the challenge of it at that point. It hurts my head thinking about trying to do it again, but I had some fun with it and it would probably come back to me as useless skill that it is like riding a bike and patting your head at the same time.
At helm, I've basically memorized the entire map at this point. Give me an island to get to and I will often sail there without needing to check the map or ask for a heading. I can generally provide headings by scanning the horizon and getting a feel of nearby island silhouettes again without needing to reference the map.
On islands, I've become known as a nut for gold hoarding. Give me an X marks the spot map and I'm generally on the Xs first dig attempt. I've basically memorized most common riddle spots and can run riddles at a relatively quick pace. I've had nights where the entire crew were on different islands (while I was on helm of the ship) doing different riddles and each riddle step I was able to offer useful headings to speed run the riddles to the crew member at each island over Discord comms. There are many puzzles in the game that I know intimately and can solve quickly.
I've become known as something of most ship's Lore Master. I've read the books and I follow the in game storytelling. I've gotten real deep into the game's Tall Tales. (I earned the gold curse doing the majority of it solo the hard way in the time before checkpoints were added to them. I fought many players and kraken to get those completions.)
Writing a novella about the Sea of Thieves seemed like an easy "win" for NaNoWriMo given how much of the lore I can explain when prompted, and how much of the map of the Sea of Thieves just lives rent free in my head at this point.
A lot of my lore hound nature has been built and tested in other games over time and Uru was a strong fandom for me. I'm sure there are blog posts to be found on that subject.
A core conceit to the lore of Uru was always that the events of the other Myst games were (somewhat apocryphal) legends of events that happened sometime in the 19th Century (the 1800s) and Uru was "real" and player actions happening "now". You, an Explorer among many peers, were personally taking a trek out to the desert in the American Southwest, finding a trail of directions to a peculiar cleft in a mountainside, solving puzzles, and from there making a winding journey down below the surface into an extraordinary Cavern full of ruins of a lost civilization.
Uru fandom was full of many different players' journals of their explorations of the Cavern, and though there are many places to reach in the game of Uru outside of that cavern in one way or another, generally the whole fictional landscape was described as "in cavern" (as opposed to real life events out of cavern; the overlap with more usual uses of the abbreviations IC and OOC amusing and intentional among that fandom).
Some players' journals got surprisingly detailed. Sometimes because they became guides/walkthroughs. Sometimes just because that's how NPCs were generally written, too, and the example was set. Sometimes just because players had fun imagining the work they put into their expeditions to the American Southwest, the gear they'd pack and the things they need to do (for safety), as if they were planning a caving expedition to Mammoth Cave in the early years of its exploration or some other similarly famous cave. A lot of the drama in cavern was the push/pull between the organization set up to try to keep people safe in their explorations and to do respectful restoration (the D'ni Restoration Council [DRC]) versus the people that just wanted freedom to explore without an oversight organization tell them not to do unsafe things. Some of the irony in players' fun journals of their explorations and how careful they planned their expeditions was directly contrasted with an early puzzle that relied on every player either having forgotten to pack something as common to cavern exploration as a flashlight or some variation of losing their flashlight or not having enough batteries or something (and the DRC was apparently quite stingy with their lovely branded cavern exploring helmets with attached flashlights).
One of the things that you pick up over the years as a Sea of Thieves crews' "Lore Master" is that all of the official lore (such as the comics, the novels, etc) happened years before players arrived in the Sea of Thieves. Most of it happened some time near the real world Golden Age of Piracy in the 18th Century (1700s). (But also some ancient civilizations older still than even those events.) Meanwhile, for very similar reasons to Uru, events that the players are participating in are "real" and happening "now".
Like Cyan Worlds with Uru, Rare wants to give Sea of Thieves players the feeling like their choices matter and their actions are really happening in the scope of the lore. Unlike Cyan Worlds, however, Rare have tried to take a strong stance against any lore happening "now" that the players aren't directly contributing to and "playing". There are no NPC journals of "today's events" for players to discover. Almost all of the "current events" lore today is word of mouth lore passed among players in game and on Discord and social media. There are NPC quest givers for some events, but in general they leave almost all of the interpretation and the choices to the players. There's a nobility of purpose there that I respect. There's a weird joy in feeling like your play matters to the overall storyline. (There was nearly an entire year of events that contributed into building up the resources of a less than respectful faction and as as a completionist event player during those events I remain amused how complicit I have been in the troubles that faction has since caused, the repercussions of which current events have been fighting.)
Unlike with Uru, there's a lot less of a journaling culture around the events in Sea of Thieves in quite the same way as Uru. With fewer NPC examples and not as much of the player base even aware that what they are doing is happening "now" (which should explain things like Halo and Gears of War themed liveries). There are some equivalents: so many of Sea of Thieves events and culture are captured daily on Twitch. I loved reading the science reports of Merfolks Lullaby. (Their weather reports especially on Sea of Thieves have been quite valuable.)
But as far as I'm aware there was a big sort of hole there that I thought would be fun to explore: how do you plan a visit to the Sea of Thieves "now"? What does that expedition look like? How did you, a wannabe pirate, fall into making such an expedition?
With my Uru experience the premise of telling that sort of expedition tale seemed "obvious" to me, especially because it would be so much harder and weirder for Sea of Thieves. It's easy enough to understand how Golden Age Pirates crossed over to the Sea of Thieves, but much more complicated to figure out how you might expect players to cross over today. Relatedly, it's easy to imagine preparing a trek to the American Southwest for spelunking alone or with friends. People do that relatively all the time. I've certainly enjoyed my random tourist trips to Mammoth Cave. Even though I take somewhat regular vacations at this point to the Caribbean it's still a lot more complicated to imagine trying to find a hidden sea in the Caribbean, much less doing so in relatively piratical garb and with an 18th Century style wooden ship. I thought there was a lot of fun spaces to world build what that would be like and why it would exist and at least some of the sorts of people that might be crazy enough to do it. So I decided to try writing a "conspiracy thriller" about visiting the Sea of Thieves.
The one stylistic choice that felt like a natural constraint given Rare's assertion that events that happen "now" should be actions of players (that left some of my beta readers unhappy) was to write it entirely in Second Person. This is the language of a Choose-Your-Adventure novel or an Interactive Fiction work where everything happens to "you". It's something I've grown up with from a young age and love. Outside of these play spaces, Second Person is extremely rare as an art form. (There's two Charlie Stross books I can point to in the science-fiction mainstream today, at least.) I had English teachers in school that loathed Second Person deeply and tried to squash my love of writing Second Person tales. I appreciate that given how rare it is outside of CYA and IF that I got immediate dislike reactions from some of my readers. I'm sorry for that, and I understand exactly where that gut reaction can come from.
I never did find counter threads in the narrative I wanted to tell to try add interesting and meaningful choices and make it an actual CYA/IF novel, but I considered it while drafting. I think it is why the working title stuck through to completion in the form that it took, it's a (single) visit to the Sea of Thieves, one of possibly many. While I didn't write more than one, I intended too, but then got cold feet.
Beyond Uru, another influence I might mention is that I'd just recently before November binged For All Mankind. I think that influence is pretty obvious when you get to it, but I felt I'd mention it as a great binge watch since it so far has escaped many people's radars.
Something that amuses me was that in trying to write myself a world building problem, I accidentally predicted a new real game feature that showed up to players later in November.
I wanted to give the impact of a Discord alliance communication in-game with the in-book restriction that there's no Discord connection on the other side of the Devil's Shroud (while in the Sea of Thieves) so I figured signal flares would easily fit the game's aesthetic and allow some distant communication. Rare added signal flares into the game inventory alongside fireworks in the following update after I'd already started making that a key part of my alliance fleet's action plan in the novella.
I like that "great minds" feeling, though some may not believe me I predicted it ahead of time. (I do somewhat regularly play Insiders builds where things are tested ahead of time to collect cosmetics in the main game. I was busy writing the novella that month and missed a few weeks of Insider play. I also tend to avoid spoilers in my Insider play as much as I can reasonably do it. My usual MO in Insiders is "to test if basic Gold Hoarding still works" and friends know I tend to refer to it as "riddle hour". I really do just grab the most basic Gold Hoarder voyages and grind X mark the spot and riddle maps just because I find it fun and relaxing and mostly avoids spoilers of new content/mechanics.)
So yeah, I wrote a fan fic novella mostly to fill a need I saw in the lore that would amuse me. I hope there's at least one reader out there with enough of a cross-over to appreciate the novel as much as I enjoyed writing it. I also hope it is a fun read whether or not someone has Sea of Thieves lore knowledge or not. As fan fic I'm letting it's primary home be AO3 for now.
]]>I tried to reduce the number of places where someone can "Well, actually" me with a technical quibble and have kept the biggest ones to parenthetical asides that should be truly optional to the description as a whole. I'd like to especially note here that: while it is very easy to make such "Well, actually" interjections in just about any technical description of anything, nuance does not imply complexity. Especially in this case it implies variation. Not every use of "blockchain" is Bitcoin, and there are certainly plenty of variations both like and unlike Bitcoin today, but Bitcoin is and likely will remain "king of blockchains" (and a use of electricity, all by itself, continuing to be larger than the electricity produced by entire "second world countries") so it remains the strongest example to use of the basics of how a blockchain operates, technically speaking.
I hope it helps. "Proving" that blockchains are devastatingly simple isn't going to change hardly any minds on their stance towards blockchains and cryptocurrencies. I just hope I've helped do my part to break down some of the salesmanship that drives cryptocurrency hype cycles ("it is so complex you just cannot understand it, so trust me"). But I'm a cynic here and think it is far too late, the majority of the damage is done, and that hype cycles or not, for better and for worse, "blockchain" is here to stay, and mostly in my opinion for the worst.
Block: you've got a bunch of documents to store that rely on/link to data from previous documents
One of the easiest ways to "link" such documents is to record in "child" documents a secure ID of the "parent" document
A common form of "secure ID" is to use the output of a "cryptographic hash function", called a hash
A hash is a relatively tiny number that reflects the contents of a larger document
What makes a hash "secure" is when you can't predict the hash of a document, but the same document will always produce the same hash, you can't predict the contents of the document from just the hash, and similar documents produce unpredictably different hashes
(That's the difference between any old "hash function" and a "cryptographic hash function", what the word "cryptographic" means here: that it meets those security needs of unpredictability)
When using a cryptographic hash function in this way to link documents, this data structure has been called a "Merkle tree" since the 1970s
It's natural shape is a tree: more than one document can point to the same parent, there's nothing in the nature of this way of storing data that prevents that from happening
In the case of most cryptocurrencies what is stored in the documents (inside of "blocks") is the transaction ledger: Transfer X amount from Address 1 to Address 2, Love Address 1
Transactions depend on previous data (need to point to parent documents; reason it forms a Merkle tree): at some point previously in the transaction ledger enough transactions sent X or more to Address 1 (and Address 1 has not already transferred that much out) so that it has X to send to Address 2 (and is a valid transaction)
Chain: creating artificial scarcity by "pruning" the (Merkle) tree
Merkle trees may have an infinite number of possible branches
Blockchains use a "consensus algorithm" to choose which branch is the preferred branch, and which other branches to ignore
(Forks are still in the same conceptual Merkle tree: the difference between the Bitcoin, Bitcoin Cash, Bitcoin Gold, etc branches is much more marketing than technical)
(Artificial scarcity: there's still an infinite possible number of branches or "forks" allowed by the nature of the underlying data structure, but marketing is great at selling "preferred options")
"Consensus algorithms" among other things declare "which document must come next"
The most common "consensus algorithm" (and arguably the only known one that seems to work in the long term so far) is Bitcoin's "Proof of Work"
"Proof of Work": the next document (block) proves you have done a lot of "hard work" in order for it to count in this branch
(In computing terms "proof of [hard] work" really is just "prove you spent enough electricity")
In Bitcoin's case the "work" to prove is completely stupid: the "secure ID" for the block needs to start with enough Zeroes
This is hard: because the secure ID is a secure hash (from a "cryptographic hash function") and the output hash is supposed to be entirely unpredictable from the input data
The only currently known way to do this is to add a bunch of random garbage next to the document (which is what truly makes it a "block": document of transactions, plus random assortment of garbage) and see if you get enough Zeroes starting the hash (ID)
Not enough Zeroes? Add different random garbage and try again (this is all the "work" in "proof of work", randomly generating literal garbage)
First one with enough Zeroes "wins"
(Everyone calls this "mining" and not "pruning" because the by-product of a "win" is new coins to spend; to incentivize computers to sit and spin creating random garbage in search of finding all these "useless zeroes" it's a very dumb lottery)
(You can't spend coins without "mining": "mining" is what adds [and reconciles] your transactions to documents in the Merkle tree branch/blocks in the "blockchain")
There's a name in security speak for this sort of hunt for a specific looking hash (ID): a preimage attack
(Technically this is a partial preimage attack because Bitcoin doesn't care what the rest of the ID is after all the zeroes it wants at a given time; but from a security perspective any partial attack is still an attack)
That's where Bitcoin's "Proof of Work" especially transcends from not just "stupid" but downright "evil" because it is a rapid, heavy attack on a cryptographic hash function used in a lot of other places as a building block of internet security
If someone found a shortcut that wasn't just the brute force "build random garbage" (why I prefaced with "the only currently known way"), they found ways to predict the outputs of the cryptographic hash function forever weakening their usefulness as secure IDs
(This has happened before. We've lost cryptographic hash functions when we've found out that they were susceptible to preimage attacks. But in those cases we had decades of warning and known better algorithms waiting in the wings. If Bitcoin miners truly "win" the preimage attack we might not have any warning at all and a lot of internet security would be immediately at risk. It's also arguable if we have known better algorithms waiting for such an eventuality today.)
]]>