<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"><title>Hyperlink Your Heart</title><link href="https://blog.hyperlinkyourheart.com/" rel="alternate"></link><link href="https://blog.hyperlinkyourheart.com/feeds/all.atom.xml" rel="self"></link><id>https://blog.hyperlinkyourheart.com/</id><updated>2026-04-20T15:31:00+02:00</updated><subtitle>Until there's nothing left.</subtitle><entry><title>M’lady Moon</title><link href="https://blog.hyperlinkyourheart.com/mlady-moon.html" rel="alternate"></link><published>2026-04-19T18:36:00+02:00</published><updated>2026-04-20T15:31:00+02:00</updated><author><name>Kevin Houlihan</name></author><id>tag:blog.hyperlinkyourheart.com,2026-04-19:/mlady-moon.html</id><summary type="html">&lt;p&gt;Thoughts on &amp;#8220;The Moon is a Harsh Mistress&amp;#8221; by Robert A.&amp;nbsp;Heinlein.&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;i class="fas fa-exclamation-triangle spoiler-icon"&gt;&lt;/i&gt;&lt;span class="spoiler-text"&gt;This post contains discussion of violence, sexual assault, and the sexual abuse of&amp;nbsp;children&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;i class="fas fa-exclamation-triangle spoiler-icon"&gt;&lt;/i&gt;&lt;span class="spoiler-text"&gt;This post contains spoilers for the novel &amp;#8220;The Moon is a Harsh Mistress&amp;#8221; by Robert A.&amp;nbsp;Heinlein&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;I read somewhere recently that &amp;#8220;The Dispossessed&amp;#8221; by Ursula K. Le Guin and &amp;#8220;The Moon is a Harsh Mistress&amp;#8221; by Robert A. Heinlein were two sides of a moon-politics coin, and since the former is one of my favourite books and I am thoroughly on-board with its politics, I decided I had to find out what the latter was&amp;nbsp;about.&lt;/p&gt;
&lt;p&gt;&lt;img alt="A cat sitting on a branch, silhouetted in front of a red-tinted full moon" src="https://blog.hyperlinkyourheart.com/images/mlady-moon/cover.jpg" title="Image by bess.hamiti@gmail.com from Pixabay"&gt;&lt;/p&gt;
&lt;p&gt;At the start of the period covered by the novel, Luna is a prison colony that is also partially populated by the free-born descendants of prisoners. It is very loosely governed by a body called the &amp;#8220;Lunar Authority&amp;#8221; which serves primarily to ensure the flow of grain from lunar farms to Earth - beyond that it doesn&amp;#8217;t really care what happens in Luna or what the inhabitants do to each other. As it monopolises trade with Earth, it is able to set prices for the grain. As I&amp;#8217;m sure you will have already guessed, it sets them too low for the tastes of the loonie farmers. Inevitably:&amp;nbsp;revolution!&lt;/p&gt;
&lt;p&gt;The narrator is a free-born computer technician named Mannie. The other main characters are Mike, the Lunar Authority central computer, which he maintains, and which he discovers has spontaneously become sentient; the Prof, the ideologue of the revolution; and Wyoh, an activist from another lunar city who becomes stranded with Mannie by circumstances. The four of them together engineer an uprising against the Authority and eventually lead a war of independence against&amp;nbsp;Earth.&lt;/p&gt;
&lt;p&gt;I found it quite an entertaining read in many respects. The first-person narration is warm and friendly, and the dialect is only occasionally impenetrable. There are aspects of the development of the sentient computer and the centralisation of computational resources that are insightful, and extremely relevant today. There are some aspects of the loonies&amp;#8217; politics and aspirations that I agree with, or am sympathetic to. Basically everybody living on the moon is mixed race and some shade of brown. I found the ending of the war quite thrilling as communications were cut off and the rocks were running&amp;nbsp;out.&lt;/p&gt;
&lt;p&gt;However, there are other parts that are uncomfortable to read, most of the politics are absurd if taken seriously, and the whole thing feels childishly naïve and&amp;nbsp;polemical.&lt;/p&gt;
&lt;h2&gt;Mike /&amp;nbsp;Michelle&lt;/h2&gt;
&lt;p&gt;At the heart of the story and the revolution it portrays is Mike (&lt;span class="caps"&gt;A.K.&lt;/span&gt;A Michelle, &lt;span class="caps"&gt;A.K.&lt;/span&gt;A Adam Selene), the computer that manages most of the operations of several lunar cities, which spontaneously becomes sentient under Mannie&amp;#8217;s&amp;nbsp;watch.&lt;/p&gt;
&lt;p&gt;Mike is an interesting and well-realised &lt;span class="caps"&gt;AI&lt;/span&gt; character: he is intelligent and widely knowledgeable from the start, but lacks social awareness and first-hand experience of what it is like to be a person. He is deeply curious about humour and plays practical jokes on people without understanding the consequences of his actions. Mannie has to train him in the difference between jokes that are always funny, those that are funny once but not a second time, and those that go too far and are not funny at all. Through this process and being introduced to additional characters he develops social awareness and a personality, or the semblance of one at&amp;nbsp;least.&lt;/p&gt;
&lt;p&gt;Because his sentience arose spontaneously, he has no loyalty to the Authority, which nominally owns and controls him. Instead he is apparently loyal to his friends - or perhaps just amusing himself by aiding them. Importantly, he is capable of dishonesty, and&amp;nbsp;deceit:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Not that Mike would necessarily give right answer; he wasn&amp;#8217;t completely&amp;nbsp;honest.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;At one point, Mike is revealed to be genderfluid, and becomes Michelle, briefly, at the request of Wyoh (Wyoming), one of Mannie&amp;#8217;s&amp;nbsp;co-conspirators:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I discussed it with Mike, what sex he was, I mean. He decided that he could be either one. So now she&amp;#8217;s Michelle and that was her&amp;nbsp;voice.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Actually, it&amp;#8217;s probably more accurate to say that Mike/Michelle is genderless, but can mimic any gender presentation that it chooses. Michelle is the second of four personas that it develops through the course of the novel, with the other three being&amp;nbsp;men.&lt;/p&gt;
&lt;p&gt;Interestingly, its choice of gender presentation completely changes how the other characters relate to it, though this is not explored in significant depth. Wyoh is initially shocked to learn that Mike has access to, and has viewed, intimate photos of her that were taken by a fertility clinic she attended. Because of course he does, he&amp;#8217;s &amp;#8220;the cloud&amp;#8221;, essentially, and he has everybody&amp;#8217;s data, and no concept of privacy or consent or personal&amp;nbsp;boundaries:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I am contract custodian of the archive files of the Birth Assistance Clinic in Hong Kong Luna. In addition to biological and physiological data and case histories the bank contains ninety-six pictures of you. So I studied&amp;nbsp;them.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;However, once she discovers that Mike can present as Michelle instead, she is suddenly completely comfortable with Michelle having viewed those images, and having intimate discussions with&amp;nbsp;her:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&amp;#8230;when she&amp;#8217;s Michelle its an entire change in manner and attitude. Don&amp;#8217;t worry about splitting her personality; she has plenty for any personality she needs. Besides, Mannie, it&amp;#8217;s much easier for both of us. Once she shifted, we took our hair down and cuddled up and talked girl talk as if we had known each other forever. For example, those silly pictures no longer embarrassed me - in fact we discussed my pregnancies quite a&amp;nbsp;lot.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Unfortunately this is where the exploration of the computer&amp;#8217;s gender is left. It is Michelle when speaking privately to Wyoh, Mike when talking to the narrator or to the three other main characters together, and Adam Selene to everybody else, for the rest of the novel. Mannie never really has to think about it aside from a brief moment of confusion, and so neither do we. Its &amp;#8220;Michelle&amp;#8221; persona is not mentioned at all after chapter&amp;nbsp;9.&lt;/p&gt;
&lt;p&gt;The centralisation of data and computation with Mike is the decisive factor in the success of the&amp;nbsp;revolution.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;And Mike took on endless new jobs. In May 2075, besides controlling robot traffic and catapult and giving ballistic advice and/or control for manned ships, Mike controlled phone system for all Luna, same for Luna-Terra voice &lt;span class="amp"&gt;&amp;amp;&lt;/span&gt; video, handled air, water, temperature, humidity, and sewage for Luna City, Novy Leningrad, and several smaller warrens (not Hong Kong in Luna), did accounting and payrolls for Luna Authority, and, by lease, same for many firms and&amp;nbsp;banks.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The Authority are essentially feeding all their data into a spy machine that is not under their control. Does that sound familiar, in 2026? In contrast to our own world, where the hoarding of computational resources and data by capital is used against users, workers, and the public interest, the Authority are so inept and oblivious that a single computer operator is able to use a similar concentration of resources against&amp;nbsp;them.&lt;/p&gt;
&lt;p&gt;There was one interaction concerning Mike&amp;#8217;s access to data that I especially enjoyed. One of the higher-ups in the Authority has stored some sensitive files on Mike, locked behind a code-word such that even Mike does not have access to it. However, there is apparently no access control around the code-word, and he is happy to tell that to anybody who wants&amp;nbsp;it:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;span class="dquo"&gt;&amp;#8220;&lt;/span&gt;Wait, Mike. Security Chief Alvarez uses you for&amp;nbsp;files?&amp;#8221;&lt;/p&gt;
&lt;p&gt;&lt;span class="dquo"&gt;&amp;#8220;&lt;/span&gt;I conjecture that to be true, since his storage location is under a locked retrieval&amp;nbsp;signal.&amp;#8221;&lt;/p&gt;
&lt;p&gt;I said, &amp;#8220;Bloody,&amp;#8221; and added, &amp;#8220;Prof, isn&amp;#8217;t that sweet? He uses Mike to keep records, Mike knows where they are - can&amp;#8217;t touch&amp;nbsp;&amp;#8216;em!&amp;#8221;&lt;/p&gt;
&lt;p&gt;&amp;#8230;&lt;/p&gt;
&lt;p&gt;I gave up. &amp;#8220;Mike, can you&amp;nbsp;explain?&amp;#8221;&lt;/p&gt;
&lt;p&gt;&lt;span class="dquo"&gt;&amp;#8220;&lt;/span&gt;I will try, Man. Wyoh, there is no way for me to retrieve locked data other than through external programming. I cannot program myself for such retrieval; my logic structure does not permit it. I must receive the signal as an external&amp;nbsp;input.&amp;#8221;&lt;/p&gt;
&lt;p&gt;&lt;span class="dquo"&gt;&amp;#8220;&lt;/span&gt;Well, for Bog&amp;#8217;s sake, what is this precious&amp;nbsp;signal?&amp;#8221;&lt;/p&gt;
&lt;p&gt;&lt;span class="dquo"&gt;&amp;#8220;&lt;/span&gt;It is,&amp;#8221; Mike said simply, &amp;#8221; &amp;#8216;special File Zebra&amp;#8217; - &amp;#8221; and&amp;nbsp;waited.&lt;/p&gt;
&lt;p&gt;&lt;span class="dquo"&gt;&amp;#8220;&lt;/span&gt;Mike!&amp;#8221; I said. &amp;#8220;Unlock Special File Zebra.&amp;#8221; He did, and stuff started spilling&amp;nbsp;out.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This put me in mind of LLMs blithely following instructions and leaking sensitive information, though they are not really all that&amp;nbsp;similar.&lt;/p&gt;
&lt;h2&gt;Women and&amp;nbsp;Girls&lt;/h2&gt;
&lt;p&gt;One of the most uncomfortable aspects of reading this book is how women are portrayed and how they are treated by the male&amp;nbsp;characters.&lt;/p&gt;
&lt;p&gt;It seems like what he was going for in their portrayal was what we would now recognise as a common conservative ideal for women: fierce and capable within their prescribed domain, leaders when necessary, but ultimately subservient to men, and happy to be&amp;nbsp;so. &lt;/p&gt;
&lt;p&gt;Meanwhile, the male characters treatment of women, almost universally, is part fawning reverence, and part adolescent lechery. Every time they perceive a woman there is an interlude of ogling and clapping and whistling, a sort of implied cartoonish slobbering that is taken to be&amp;nbsp;flattering.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Wyoming came out - and I didn&amp;#8217;t recognize her. Then did and stopped to give full applause. Just had to - whistles and finger snaps and moans and a scan like mapping&amp;nbsp;radar.&lt;/p&gt;
&lt;p&gt;&amp;#8230;&lt;/p&gt;
&lt;p&gt;She waited, big smile on face and body undulating, while I applauded. Before I was done, two little boys flanked me and added shrill endorsements, along with clog&amp;nbsp;steps.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Prof even gives Michelle a somewhat subdued version of the same treatment the only time he hears her&amp;nbsp;voice:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I had to explain to Prof who &amp;#8220;Michelle&amp;#8221; was and introduce him. He was formal, sucking air and whistling and clasping hands - sometimes I think Prof was not right in his&amp;nbsp;head.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Props for accepting her transition I&amp;nbsp;guess&amp;#8230;&lt;/p&gt;
&lt;p&gt;I think what bothers me most about this treatment is that there are no characters who object to it. The woman we spend the most time with, Wyoh, is a radical political activist, and she appears to be completely fine with it. Her political aspirations are laser-focused on overthrowing the Authority, without a thought to women&amp;#8217;s place in society or their treatment. In fact, she seems to love every instance where she is objectified and&amp;nbsp;man-handled.&lt;/p&gt;
&lt;p&gt;This might be the result of the story being told from the point of view of one oblivious man who simply doesn&amp;#8217;t notice that the women around him are unhappy, but I think in that case there would be hints of it to the reader that go over his head, and there are not, at least not that I noticed. Rather, I think none of the women have any complaints because the author thinks they have a good deal in the society he is portraying, and the over-the-top attention is part of&amp;nbsp;that.&lt;/p&gt;
&lt;p&gt;The other benefits of that &amp;#8220;deal&amp;#8221; that are highlighted are that women have significant power over men in their domestic arrangements (taking on additional husbands for example), and that violence towards women is not tolerated, because other men would not tolerate&amp;nbsp;it:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Stu, is no rape in Luna. None. Men won’t permit. If rape had been involved, they wouldn&amp;#8217;t have bothered to find a judge and all men in earshot would have scrambled to&amp;nbsp;help.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I like the idea that their culture could have shifted to such an extent that rape has become unthinkable. I think libertarian-socialist politics also requires such changes in culture to work - a near-universal acceptance of other people&amp;#8217;s personhood and respect for their autonomy. In this case, however, the acceptance is not of women&amp;#8217;s personhood - the reason raping them is unacceptable is that they are a scarce&amp;nbsp;resource:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Here we are, two million males, less than one million females. A physical fact, basic as rock or vacuum. Then add idea of [there ain&amp;#8217;t no such thing as a free lunch]. When thing is scarce, price goes up. Women are scarce; aren&amp;#8217;t enough to go around - that makes them most valuable thing in Luna, more precious than ice or air, as men without women don’t care whether they stay alive or&amp;nbsp;not.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Fuckin&amp;#8217; &lt;em&gt;barf&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;But it&amp;#8217;s even worse than that. The context of the quotes above is that this tourist from Earth, Stu, was nearly executed for grabbing and trying to kiss a fourteen year old girl without her consent. Mannie&amp;#8217;s assertion that there is &amp;#8220;no rape in Luna&amp;#8221; is actually the typical rape-apologist&amp;#8217;s tactic of redefining it to exclude sex with minors or forms of coercion that are not overtly&amp;nbsp;violent.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Lajoie shivered. &amp;#8220;At her age? It scares me to think of it. She’s below the age of consent. Statutory&amp;nbsp;rape.&amp;#8221;&lt;/p&gt;
&lt;p&gt;&lt;span class="dquo"&gt;&amp;#8220;&lt;/span&gt;Oh, bloody! No such thing. Women her age are married or ought to be. Stu, is no rape in&amp;nbsp;Luna.&amp;#8221;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Of course, how would we know we were reading &amp;#8220;libertarian&amp;#8221; literature if there wasn&amp;#8217;t an attack on the idea that children can&amp;#8217;t consent to sex? Mannie&amp;#8217;s youngest wife is fifteen, was raised by the family that she later married into, and is pregnant by one of the men who raised her - her &amp;#8220;father&amp;#8221;, though not by blood. Mannie doesn&amp;#8217;t consider the possibility that she might have been groomed by one (or more) of the men in the family - nor does Wyoh, when the situation is described to&amp;nbsp;her.&lt;/p&gt;
&lt;p&gt;I should note that the child-rape is not limited to girls. Mannie himself was married, or &amp;#8220;opted&amp;#8221;, as the book puts it, at fourteen, and presumably began sexual relationships with several adult women at that&amp;nbsp;point.&lt;/p&gt;
&lt;p&gt;In a particularly uncomfortable encounter, the first time Wyoh meets the Prof she &amp;#8220;jokingly&amp;#8221; accuses Mannie of having raped her the previous night. This is &amp;#8220;revenge&amp;#8221; for his failure to proposition her, which she took as an insult. This goes on for two pages and is repeated again in the next chapter. In a world where such an accusation is apparently a death sentence, what does this tell us? That the author views women as childish and unaccountable. It also tells us that men can hear rape accusations against their friends and not take them seriously. It seems like Mannie might be an unreliable narrator on this topic specifically, and there is a complete absence of female perspectives on it in the&amp;nbsp;novel.&lt;/p&gt;
&lt;p&gt;The idea that women have all the power in relationships and the idea that girls are routinely married at fourteen seem at odds to me. To me they suggest a society where women view &lt;em&gt;themselves&lt;/em&gt; as scarce resources, and their daughters as well - resources that have to start being exploited as soon and as often as possible. Wyoh reinforces this notion in describing renting her womb as a professional&amp;nbsp;surrogate:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I stopped feeling that I was a failure as a woman. I made more money than I could ever hope to earn at other jobs. And my time almost to myself; having a baby hardly slows me down - six weeks at most and that long only because I want to be fair to my clients; a baby is a valuable&amp;nbsp;property.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Politics&lt;/h2&gt;
&lt;p&gt;We might split the politics of the book into two - on one side, the politics and social relations of the de-facto &amp;#8220;anarchist&amp;#8221; society of Luna outside of the Authority&amp;#8217;s remit - on the other, the politics of the revolutionaries, and how they conduct their&amp;nbsp;revolution.&lt;/p&gt;
&lt;p&gt;What we learn of how lunar society is structured is patchy. Agriculture appears to be the primary economic activity, and it is carried out by family businesses of various sizes. The Authority squeezes these by monopolising access to water and by being the only buyer of their&amp;nbsp;output.&lt;/p&gt;
&lt;p&gt;The families in question are commonly plural in nature. These come in various forms, most of which are not described, but the one Mannie is involved in is called a &amp;#8220;line&amp;#8221; marriage, which has the benefit of resulting in a stable accumulation of capital that can persist for generations, where young spouses are added periodically. The ratio of men to women in Mannie&amp;#8217;s family at least is close to 1:1 - in fact there are more women than men at the time the novel is set. This suggests that line marriages have little to do with adapting to the uneven gender ratio of Lunar society. Rather their purpose is specifically to accumulate capital, and thereby be attractive to young women who are looking for stability; or in other words, to enable a small number of wealthy capitalists to acquire a disproportionate share of the scarce resource that they consider women to&amp;nbsp;be.&lt;/p&gt;
&lt;p&gt;There is a working class, of course. Apparently they are always in demand and live high on the hog, and that&amp;#8217;s about all we hear about&amp;nbsp;them.&lt;/p&gt;
&lt;p&gt;There is extensive discussion of how norms (not laws - &lt;em&gt;it&amp;#8217;s very important that you not call them laws&lt;/em&gt;) are enforced, or how &lt;em&gt;justice&lt;/em&gt; is achieved. In short: murder, or the threat thereof, violence short of murder, and ostracism. According to Mannie, this corrective violence used to be excessive, but over time the worst elements were eliminated, and the rest adjusted their behaviour to avoid violence. Here are several descriptions of this violence from different parts of the&amp;nbsp;novel:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;One first thing learned about Luna, back with first shiploads of convicts, was that zero pressure was place for good manners. Bad-tempered straw boss didn&amp;#8217;t last many shifts; had an &amp;#8220;accident&amp;#8221; - and top bosses learned not to pry into accidents or they met accidents, too. Attrition ran 70 percent in early years - but those who lived were nice&amp;nbsp;people.&lt;/p&gt;
&lt;p&gt;&amp;#8230;&lt;/p&gt;
&lt;p&gt;All our customs work that way. If you’re out in field and a cobber needs air, you lend him a bottle and don’t ask cash. But when you’re both back in pressure again, if he won’t pay up, nobody would criticize if you eliminated him without a&amp;nbsp;judge.&lt;/p&gt;
&lt;p&gt;&amp;#8230;&lt;/p&gt;
&lt;p&gt;If you eliminate a man other than self-defense, you pay his debts and support his kids, or people won’t speak to you, buy from you, sell to&amp;nbsp;you.&lt;/p&gt;
&lt;p&gt;&amp;#8230;&lt;/p&gt;
&lt;p&gt;But we figure this way: If a man is killed, either he had it coming and everybody knows it - usual case - or his friends will take care of it by eliminating man who did it. Either way, no problem. Nor many&amp;nbsp;eliminations.&lt;/p&gt;
&lt;p&gt;&amp;#8230;&lt;/p&gt;
&lt;p&gt;They get so anxious they will kill for it&amp;#8230; and from stories old-timers tell was killing enough to chill your teeth in those days. But after a while those still alive find way to get along, things shake down. As automatic as gravitation. Those who adjust to facts stay alive; those who don’t are dead and no&amp;nbsp;problem.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;He insists that such violence is now rare because everybody is so well behaved, but he is so aware of the consequences of violating norms that it is hard to believe he is not seeing these consequences constantly. Fully half of new arrivals in Luna die, either by accident or violence, with the two being equated as &amp;#8220;natural&amp;nbsp;hazards&amp;#8221;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&amp;#8230;Luna (yes, and sometimes Luna&amp;#8217;s Loonies) killed about half of new&amp;nbsp;chums.&lt;/p&gt;
&lt;p&gt;&amp;#8230;&lt;/p&gt;
&lt;p&gt;Luna has only one way to deal with a new chum: Either he makes not one fatal mistake, in personal behavior or in coping with environment that will bite without warning&amp;#8230; or he winds up as fertilizer in tunnel&amp;nbsp;farm.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;It&amp;#8217;s very much &amp;#8220;nobody is violent here - if they were, we&amp;#8217;d kill&amp;nbsp;&amp;#8216;em!&amp;#8221;&lt;/p&gt;
&lt;p&gt;Such violence is not limited to punishment for assault, murder, or refusal to pay debts. On numerous occasions, Mannie muses about others deserving death merely for being rude. On one occasion, a man is murdered by some agents of the revolutionary state for mocking them, and Mannie supports that, even going so far as to suggest a eugenicist&amp;nbsp;justification.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;They did beautifully. But idiots made fun of them - &amp;#8220;play soldiers,&amp;#8221; &amp;#8220;Adam&amp;#8217;s little apples,&amp;#8221; other names. A team was going through a drill, showing they could throw a temporary lock around one that had been damaged, and one of these pinheads stood by and rode them&amp;nbsp;loudly.&lt;/p&gt;
&lt;p&gt;Civil Defense team went ahead, completed temporary lock, tested it with helmets closed; it held - came out, grabbed this joker, took him through into temporary lock and on out into zero pressure, dumped&amp;nbsp;him.&lt;/p&gt;
&lt;p&gt;Belittlers kept opinions to selves after that. Prof thought we ought to send out a gentle warning not to eliminate so peremptorily. I opposed it and got my way; could see no better way to improve breed. Certain types of loudmouthism should be a capital offense among decent&amp;nbsp;people.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;As I mentioned above, the introduction of one major character, Stu, sees him almost executed for attempting to kiss a fourteen year old girl against her will. He is rescued from this fate by Mannie, which gives us a glimpse of what passes for a justice system in Luna. Basically, if somebody perceives an infraction of the unwritten social code or faux pas that warrants corrective action, they &amp;#8220;should&amp;#8221; involve an impartial third party that they pay to give a judgement on the matter. This could be a professional judge, or it could be anybody (the characters looking to execute Stu are looking for a professional and settle for Mannie when they can&amp;#8217;t find one). The accused and the &amp;#8220;prosecutors&amp;#8221; (it&amp;#8217;s hard to know what terms to use) both have to agree to the choice of judge, and they both have to pay him, with the amount required depending on the severity of the punishment sought. Optionally there is a jury, paid for by the judge out of his fee, comprised of random passers by, whose opinions are just ignored if they are not what the judge wants to&amp;nbsp;hear.&lt;/p&gt;
&lt;p&gt;The idea of this farcical proceeding is that if somebody objects to the execution, the executioners can point to the process, and the fact that the accused submitted to it, as evidence that the execution was justified - thereby preempting a cycle of retributive violence. However, it seems very likely to me that poor or poorly connected people would not see anything like justice from this system. Equally obvious to me are the possibilities for powerful families or individuals to off rivals and enemies with the smokescreen of justice through a false accusation and a bribe - or to simply evade the consequences of their actions, just as the wealthy and connected do&amp;nbsp;today.&lt;/p&gt;
&lt;p&gt;On another occasion, it is used to justify escalating the punishment of a group of rapists from a quick and simple shooting to prolonged torture by a mob. This treatment might be deserved, perhaps even necessary, as the rape in question was what incited the uprising - my point is that the conclusion was foregone, and the judicial process completely&amp;nbsp;pointless.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Finn decided that shooting was too good for them, so he went judge and used his squad as&amp;nbsp;jury.&lt;/p&gt;
&lt;p&gt;They were stripped, hamstrung at ankles and wrists, turned over to women in&amp;nbsp;Complex.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Execution isn&amp;#8217;t the only possible punishment. Mannie chooses to levy a fine against Stu &lt;em&gt;and&lt;/em&gt; his accusers. The fine is payable to his revolutionary organisation. It is unclear to whom it would be payable if a professional judge had been used. Would the judge just take it? Paying a victim some restitution would maybe make sense but this isn&amp;#8217;t suggested as a possibility at&amp;nbsp;all.&lt;/p&gt;
&lt;p&gt;Again, we have an unreliable narrator, in Mannie, on all of these matters. He is a member of one of Luna&amp;#8217;s longest lived line marriages. His family controls a significant amount of capital, and is likely well connected. This ad-hoc system of justice serves his interests well enough, so he dismisses the possibility that it might not serve&amp;nbsp;everybody.&lt;/p&gt;
&lt;p&gt;What of the politics of the revolutionaries, then? Wyoh describes herself as a &amp;#8220;Fifth Internationalist&amp;#8221;, and describes that as a united front of Communists, Fourth Internationalists, and some other persuasions that sound like they might be leftist - but emphatically not Marxist. This is apparently the dominant ideology in the revolutionary organisation that precedes the one Mike founds. However at a political meeting that we witness, the only concerns expressed are those of small business owners, and Wyoh&amp;#8217;s solution to all their problems is to get rid of the Authority and have free&amp;nbsp;markets.&lt;/p&gt;
&lt;p&gt;If we accept that Wyoh is vaguely leftist, she is the only one in the upper echelons of the revolutionary organisation. At the other end of the spectrum is the Prof, who describes himself as a &amp;#8220;rational anarchist&amp;#8221; who can &amp;#8220;get along with a randite&amp;#8221;. He describes Thomas Jefferson as the first rational anarchist, which seems like a strange way to describe somebody who was a &lt;span class="caps"&gt;US&lt;/span&gt; president and owned slaves (even if he felt bad about it), but it becomes easier to understand when we see how the Prof conducts the revolution. In essence he is an individualist who is utterly contemptuous of democracy and anything else that might prevent him from getting his way - which is, again, free&amp;nbsp;markets.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;span class="dquo"&gt;&amp;#8220;&lt;/span&gt;You are right that the Authority must go. It is ridiculous - pestilential, not to be borne - that we should be ruled by an irresponsible dictator in all our essential economy! It strikes at the most basic human right, the right to bargain in a free&amp;nbsp;marketplace.&amp;#8221;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Here he describes how a successful revolution should be&amp;nbsp;conducted:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;span class="dquo"&gt;&amp;#8220;&lt;/span&gt;Wyoming dear lady, revolutions are not won by enlisting the masses. Revolution is a science only a few are competent to practice. It depends on correct organization and, above all, on communications. Then, at the proper moment in history, they strike. Correctly organized and properly timed it is a bloodless&amp;nbsp;coup.&amp;#8221;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;On other occasions he expresses minarchist views, and, of course, anti-taxation&amp;nbsp;views.&lt;/p&gt;
&lt;p&gt;Stu, the newest Loonie, is a monarchist. Just that. Just wants a king. And a fourteen year old wife, I&amp;nbsp;guess.&lt;/p&gt;
&lt;p&gt;Our narrator, meanwhile, is nominally apolitical, but really a sort of smug conservative cynic. He likes the status quo just fine except for the Authority, but he just ignores it as best he can. He doesn&amp;#8217;t care about anybody&amp;#8217;s freedom or interests aside from his own and is dismissive of any claim that political change is possible. He thinks anybody who does not thrive in the current order is an idiot, because they should just do what he did - marry into generational wealth and steal public goods as his family does - even as he clearly understands that it would not work at all for everybody to do&amp;nbsp;that.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I was not dissatisfied back when we were &amp;#8220;ground under Iron Heel of Authority.&amp;#8221; I cheated Authority and rest of time didn&amp;#8217;t think about it. Didn&amp;#8217;t think about getting rid of Authority - impossible. Go own way, mind own business, not be&amp;nbsp;bothered&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Mostly the views we hear are the Prof&amp;#8217;s and Mannie&amp;#8217;s, with Wyoh serving as a naïve foil early on and later fading into the background to deal with organising the women. What we do hear of explicit political discussion feels really polemical and often disconnected from the reality of the world presented by the novel. For example, immediately after Wyoh describes her political affiliation the Prof asks her what her views on capital punishment are - would she execute traitors to a free Luna? He doesn&amp;#8217;t explain what treachery would mean in his anarchy, but he is very clear that he supports capital punishment - except that he would serve as judge and executioner (rather than, it is implied, a state). But that&amp;#8217;s just the current situation in Luna! You can already just murder people if they wrong you or do something you don&amp;#8217;t like! Neither of them point this out. Why is this even his overriding concern, when things already work the way he wants? For some reason Wyoh has no thoughts on what passes for justice in Luna, and can&amp;#8217;t say whether she wants a state or not in clear terms. The early revolutionaries like Wyoh are supposedly leftists but they have nothing to say about the norms of lunar society, like the rampant terror and violence, or anything about how it should be reshaped, they just want to get rid of the Authority and have free markets. They don&amp;#8217;t even rise to the level of straw-men, their views are so thinly realised. The Prof is portrayed as an iconoclastic radical and political genius for expressing goals that have already been 99% achieved, and he also just wants to get rid of the Authority and have free markets, with the twist of also wanting Luna to be self-sufficient, for ecological&amp;nbsp;reasons.&lt;/p&gt;
&lt;p&gt;The revolution these people conduct is thoroughly hierarchical from start to finish. There is no unity of means and ends here. With Mike as a trusted oracle to facilitate organisation, the trio of Mannie, the Prof and Wyoh form a new revolutionary organisation with themselves as the top level and a pyramidal structure below them where each level recruits a larger level of subordinates. The lower levels are explicitly just grunts. Mike assigns codenames and facilitates communications in such a way that nobody in the organisation knows more than a couple of other members. Their strategy does not involve educating and organising the apathetic masses, but secretly manipulating the Authority into provoking them to anger: accelerationism, in other words. This is enabled by Mike&amp;#8217;s ubiquitous surveillance, censorship and manipulation of communications, secret theft of funds from every business whose accounts he has access to, and adoption of several personae to serve as figureheads and spread&amp;nbsp;propaganda.&lt;/p&gt;
&lt;p&gt;The trio at the upper level of the organisation are not even equals, really. As I noted above, Wyoh is sidelined, and apparently content to be so. The Prof constantly manipulates Mannie and leaves him in the dark about plans until it is too late to back out or offer any input on them, or even until the consequences are already realised. Really it is the Prof and Mike who are in charge - or perhaps just Mike, as he is the biggest, smartest boy who has already calculated the odds on every possible action, and the rest inevitably go along with his&amp;nbsp;prescriptions.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;span class="dquo"&gt;&amp;#8220;&lt;/span&gt;Please, Manuel. Keeping you temporarily in the dark greatly enhanced our chances; you can check this with&amp;nbsp;Adam.&amp;#8221;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Mike addresses the lunar population after the success of the uprising, and describes the political situation and agenda as one of complete privatisation of the functions of the&amp;nbsp;state:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;To take on temporarily those necessary functions of the defunct Authority I have asked the General Manager of LuNoHo Company to serve. This company will provide temporary supervision and will start analyzing how to do away with the tyrannical parts of the Authority and how to transfer the useful parts to private&amp;nbsp;hands.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The socialists who were the dominant anti-Authority political force prior to Mike coming on the scene, including Wyoh, apparently have nothing to say about&amp;nbsp;this.&lt;/p&gt;
&lt;p&gt;After the Authority is overthrown the Prof organises an interim congress, nominally to organise the post-Authority state, but really to distract and neuter any politically motivated people who may disagree with his views. He describes its purpose and participants to&amp;nbsp;Mannie:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;But Prof didn&amp;#8217;t get excited; he went on smiling. &amp;#8220;Manuel, do you really think that mob of retarded children can pass any&amp;nbsp;laws?&amp;#8221;&lt;/p&gt;
&lt;p&gt;&lt;span class="dquo"&gt;&amp;#8220;&lt;/span&gt;You told them to. Urged them&amp;nbsp;to.&amp;#8221;&lt;/p&gt;
&lt;p&gt;&lt;span class="dquo"&gt;&amp;#8220;&lt;/span&gt;My dear Manuel, I was simply putting all my nuts in one basket. I know those nuts; I&amp;#8217;ve listened to them for years. I was very careful in selecting their committees; they all have built-in confusion, they will&amp;nbsp;quarrel.&lt;/p&gt;
&lt;p&gt;&amp;#8230; I almost needn&amp;#8217;t have bothered; more than six people cannot agree on anything, three is better—and one is perfect for a job that one can do. This is why parliamentary bodies all through history, when they accomplished anything, owed it to a few strong men who dominated the&amp;nbsp;rest.&amp;#8221;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;He seems to be dismissive of the idea that any form of collective decision-making is possible. Later he manipulates this congress into signing a declaration of independence attributed to Thomas Jefferson and modernised by himself, without amendment. This supposed anarchist has created a state, and a deep state, where the only person with any say is&amp;nbsp;himself.&lt;/p&gt;
&lt;p&gt;Stu captures the absurdity of the revolution perfectly when, after a discussion about the difference between taxation and the rampant theft they have engaged in to fund it, he proposes to nominate the Prof to be Luna&amp;#8217;s first monarch, a role which he sees the supposed anarchist fulfilling perfectly, and entirely consistent with his actions up to that&amp;nbsp;point:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;span class="dquo"&gt;&amp;#8220;&lt;/span&gt;No, but now that Congress has taken up the matter of a constitution I intend to find time to attend sessions. I plan to nominate you for&amp;nbsp;King.&amp;#8221;&lt;/p&gt;
&lt;p&gt;Prof looked shocked. &amp;#8220;Sir, if nominated, I shall repudiate it. If elected, I shall&amp;nbsp;abdicate.&amp;#8221;&lt;/p&gt;
&lt;p&gt;&lt;span class="dquo"&gt;&amp;#8220;&lt;/span&gt;Don&amp;#8217;t be in a hurry. It might be the only way to get the sort of constitution you want. And that I want, too, with about your own mild lack of enthusiasm. You could be proclaimed King and the people would take you; we Loonies aren&amp;#8217;t wedded to a&amp;nbsp;republic.&lt;/p&gt;
&lt;p&gt;&lt;span class="dquo"&gt;&amp;#8220;&lt;/span&gt;When the time comes, you won&amp;#8217;t be able to refuse. Because we need a king and there isn&amp;#8217;t another candidate who would be accepted. Bernardo the First, King of Luna and Emperor of the Surrounding&amp;nbsp;Spaces.&amp;#8221;&lt;/p&gt;
&lt;p&gt;&lt;span class="dquo"&gt;&amp;#8220;&lt;/span&gt;Stuart, I must ask you to stop. I&amp;#8217;m becoming quite&amp;nbsp;ill.&amp;#8221;&lt;/p&gt;
&lt;p&gt;&lt;span class="dquo"&gt;&amp;#8220;&lt;/span&gt;You&amp;#8217;ll get used to it. I&amp;#8217;m a royalist because I&amp;#8217;m a democrat. I shan&amp;#8217;t let your reluctance thwart the idea any more than you let stealing stop&amp;nbsp;you.&amp;#8221;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Heinlein makes the case quite well that ends follow means in their nature. The revolutionaries manipulate and deceive the masses and end up at odds with them, because they didn&amp;#8217;t bring them along or listen to them. They centralise power in a state, and end up with a state. They invest so heavily in figureheads, and grant so much authority to a single man, that they may as well have a&amp;nbsp;king.&lt;/p&gt;
&lt;h2&gt;What&amp;#8217;s the&amp;nbsp;difference?&lt;/h2&gt;
&lt;p&gt;One thing I will say for the novel is that it made me think about some of the differences between right-wing &amp;#8220;libertarianism&amp;#8221; and libertarian socialism or anarchism, particularly where they would, at a glance, appear to overlap. Anarchists also propose a society without laws or any body with a monopoly on violence to enforce them or anything resembling them, such as social&amp;nbsp;norms.&lt;/p&gt;
&lt;p&gt;&lt;span class="dquo"&gt;&amp;#8220;&lt;/span&gt;How would an anarchist society enforce traffic laws?&amp;#8221; and &amp;#8220;how would an anarchist society deal with rapists and other violent criminals?&amp;#8221;, and similar, are perennial questions in &amp;#8220;anarchism 101&amp;#8221; type spaces online. I read an &lt;a href="https://www.reddit.com/r/Anarchy101/comments/1qkvd7g/comment/o19rp9i/" title="Answer regarding traffic laws on r/Anarchy101"&gt;interesting answer on the former question&lt;/a&gt; recently. If the goal is increasing public safety, simply demanding that people obey certain rules is not very effective. Even now, with police and speed cameras to catch people, courts to fine them and prisons to throw them in, people still speed when they think they can get away with it. A better approach would be to design roads in such a way that people can&amp;#8217;t speed, cars in such a way that they are safer for everybody in collisions, and cities such that cars are no longer a necessity, or become an unattractive choice. These options would all be available to communities and collectives in an anarchist society - the collective will enforced by &lt;em&gt;design&lt;/em&gt;, rather than by laws and&amp;nbsp;police.&lt;/p&gt;
&lt;p&gt;Do we want a society where rapists get violently punished, or do we want one where nobody gets raped? Anarchists don&amp;#8217;t generally eschew violence for individual or community self-defence, but I don&amp;#8217;t think many would say that they want to create a society where beating and murdering rapists is a constant necessity, or even one where people inclined to rape refrain merely because they fear punishment - because if that is their only reason, then they will do it when they think they can get away with it, just like they do&amp;nbsp;now.&lt;/p&gt;
&lt;p&gt;It&amp;#8217;s harder to imagine this problem being addressed by urban or industrial design - but cultures can be &lt;em&gt;designed&lt;/em&gt; as well. There have been many times when cultures have been shaped by conscious effort; by states with nationalist ambitions and by grassroots campaigns with liberatory&amp;nbsp;goals.&lt;/p&gt;
&lt;p&gt;In one controversial scene in The Dispossessed, Shevek assaults an Urrasti woman; he is unable to control himself when she comes on to him, due to a mix of her enhanced and sexualised femininity - something alien to Anarres - and being inebriated for the first time. I do not like the implication of this, that women should have to suppress their femininity because men are unable to control themselves. Gender roles and gender presentation will undoubtedly change in the future, perhaps even converge to some extent, but prescribing such changes doesn&amp;#8217;t seem compatible with anarchist ideology, or any other left-wing ideology. However, it does make the case that &lt;em&gt;even if&lt;/em&gt; men have an inherent proclivity for sexual violence, that proclivity can be controlled by culture to prevent it from violating other people&amp;#8217;s&amp;nbsp;freedom.&lt;/p&gt;
&lt;p&gt;In another scene, some children raised on Anarres experiment with authority and incarceration. They are familiar with these concepts in an abstract way, but have no direct experience with them, no idea what it means to confine somebody or be confined. They are disturbed by the experiment, regardless of the role they played in&amp;nbsp;it.&lt;/p&gt;
&lt;p&gt;Revolutions involve more than just changing the government or replacing the structures of the state with slightly different ones. They require deep and broad changes to the culture and the prevailing values of society, if they are to succeed. An anarchist society may have mechanisms to deal with violence when it occurs, but, more importantly, it would have a culture where intentionally depriving somebody else of their autonomy would be almost unthinkable - almost as injurious to the perpetrator as to the&amp;nbsp;victim.&lt;/p&gt;
&lt;p&gt;I would say that the anarchist approach to violence, sexual or otherwise, is to prevent it in the first place by addressing its root causes; by healing the wounds of hierarchy, patriarchy, alienation, and want. We might not need explicit laws about statutory rape if the autonomy of children is widely respected, if they are taught to question and advocate for themselves rather than to blindly submit to authority, if their family and community protects them, if they are not seen as resources to be exploited - and, it goes without saying, if girls are encouraged to have higher aspirations for themselves than to get married at fourteen and start spitting out&amp;nbsp;babies.&lt;/p&gt;
&lt;p&gt;But isn&amp;#8217;t this the same as the Non-Aggression Principle that libertarians are always going on about? Not really. It may be superficially similar in being a proposed change in the predominant values of society that would enable it to function without laws or institutions with a monopoly on violence, but that is where the similarity ends. The &lt;span class="caps"&gt;NAP&lt;/span&gt;, like everything else in right-wing libertarianism, is about property rights, and sees even people as &amp;#8220;self-possessed&amp;#8221; property. It doesn&amp;#8217;t have any critique of the hierarchies inherent to capitalism. They may see something like pollution as aggression if it harms other people, but withholding food from the starving or shelter from the homeless is not - that is just the exercise of property rights. They don&amp;#8217;t reckon with the tendency of capital to accumulate in fewer and fewer hands and the inevitable conflict that creates between social classes - between those who own all the property and those who need access to that property to survive. The purpose of the &lt;span class="caps"&gt;NAP&lt;/span&gt; is to allow capitalists to do what they want with their property, including the exploitation of a desperate, propertyless class, while anything that class does to advance their interests, or that individuals do even just to prevent their own deaths, can be portrayed as &amp;#8220;aggression&amp;#8221; if it &amp;#8220;harms&amp;#8221; that property, allowing a response&amp;nbsp;in-kind.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;I watched an Angela Collier video recently where she talked about how she really enjoyed Atlas Shrugged when she read it, because she knew nothing about it in advance and assumed it was satirical based on its contents. I can&amp;#8217;t claim to have had the same lack of awareness about this book - the reason I wanted to read it was because it was contrasted with The Dispossessed, after all; I knew that it was about right-wing libertarianism, or portrayed a society inspired by that&amp;nbsp;ideology.&lt;/p&gt;
&lt;p&gt;Contrary to my expectations, it seems like an indictment of those ideas: that a society without laws and a body to enforce them, or any alternative mechanisms of organisation and dispute resolution, has serious failures, absurd and obvious failures. That libertarians are self-serving hypocrites, closeted monarchists and crypto-fascists. It seems like a work that is ridiculing anarcho-capitalists, and saying that their ideas are impossible to achieve, or can only be achieved temporarily in very unique circumstances, by means that nobody would ever want to endure. It reads like a satire to me, though I have never heard anybody describe it as&amp;nbsp;such.&lt;/p&gt;
&lt;p&gt;I mean just look at how the Prof&amp;#8217;s career is summarised when he is first&amp;nbsp;introduced:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;No doubt he could have gone to work in any school then in L-City but he didn&amp;#8217;t. He worked a while washing dishes, I&amp;#8217;ve heard, then as babysitter, expanding into a nursery school, and then into a creche. When I met him he was running a creche, and a boarding and day school, from nursery through primary, middle, and high schools, employed co-op thirty teachers, and was adding college&amp;nbsp;courses.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This guy bootstrapped himself from a babysitting gig to running a university, essentially. This is joke, right? This has to be a joke. It&amp;#8217;s hilarious! It&amp;#8217;s the kind of joke that somebody would make if they were mocking libertarians. But is it also the kind of joke that libertarians make to poke fun at&amp;nbsp;themselves?&lt;/p&gt;
&lt;p&gt;So is this even a libertarian novel, in the vein of Atlas Shrugged? Some seem to think so. In &lt;a href="https://mises.org/mises-daily/was-robert-heinlein-libertarian" title="Was Robert A. Heinlein a Libertarian? - mises.org"&gt;this article&lt;/a&gt; on mises.org, Jeff Riggenbach credits it with inspiring a large number of influential libertarians, and&amp;nbsp;states:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The Moon Is a Harsh Mistress is unquestionably a libertarian novel. It is unquestionably one of the three or four most influential libertarian novels of the last&amp;nbsp;century.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Whatever his intentions, I think Heinlein did accurately assess many of the implications of right-wing libertarian ideology. I think it says more than I ever could about the nature of that ideology, that this is a novel that they think represents their views in a positive&amp;nbsp;light.&lt;/p&gt;</content><category term="Books"></category><category term="politics"></category><category term="sci-fi"></category><category term="fiction"></category></entry><entry><title>Value and Values</title><link href="https://blog.hyperlinkyourheart.com/values.html" rel="alternate"></link><published>2025-11-30T13:42:00+01:00</published><updated>2025-11-30T17:25:00+01:00</updated><author><name>Kevin Houlihan</name></author><id>tag:blog.hyperlinkyourheart.com,2025-11-30:/values.html</id><summary type="html">&lt;p&gt;Some reflections on what tech companies actually value, with particular reference to Framework&amp;#8217;s recent sponsorship choices, but really more generally than&amp;nbsp;that.&lt;/p&gt;</summary><content type="html">&lt;p&gt;I&amp;#8217;ve been a Framework enjoyer for a couple of years now. Indeed, I&amp;#8217;m writing this on my Framework 13. I didn&amp;#8217;t buy it because it occupied the best position in the performance/portability/price matrix of the options available to me. I bought it because it offered decent performance and portability while potentially being significantly more repairable and upgradeable than its competitors, and because they put some effort into ensuring Linux is well supported. In other words, it embodied my values in ways that other laptops did not, and I thought that was worth paying a premium for. The value proposition is in the values of the company. I&amp;#8217;m careful to point this out when recommending it to people because I don&amp;#8217;t want them to think they are buying a &amp;#8220;MacBook but&amp;nbsp;repairable&amp;#8221;.&lt;/p&gt;
&lt;p&gt;&lt;img alt="My Framework 13 on a table with its lid slightly open, casting an orange glow on the palm rest. It has an orange bezel and a black DBrand skin." src="https://blog.hyperlinkyourheart.com/images/values/myframework.jpg" title="My Framework 13. Repairable. Not a MacBook."&gt;&lt;/p&gt;
&lt;p&gt;Of course I was aware that there were &amp;#8220;risks&amp;#8221; involved. The main one that came to mind was that they might go out of business at some point and that would likely be the end of any significant repairability advantage, as despite the clever design, it is not commodity hardware as you would find in a desktop &lt;span class="caps"&gt;PC&lt;/span&gt;. Or of course, they might just abandon the model I have eventually, or abandon their current focus altogether. Any of these could happen if their business model and the niche they occupy turns out to not be sustainable, or profitable, or they fail to grow that niche market. In such an event, it would be the same as if I had bought any other laptop. They joke all the time about doing an &lt;span class="caps"&gt;IPO&lt;/span&gt;, something which might significantly effect their ability to stick to their principles, and it comes across very &amp;#8220;ha ha, only&amp;nbsp;serious&amp;#8221;.&lt;/p&gt;
&lt;p&gt;One risk that hadn&amp;#8217;t really occurred to me was that they would &lt;a href="https://community.frame.work/t/framework-supporting-far-right-racists/75986" title="Community forum thread about Framework sponsorship issues."&gt;associate their brand with fascism&lt;/a&gt; in a ridiculous &lt;span class="caps"&gt;PR&lt;/span&gt;&amp;nbsp;own-goal.&lt;/p&gt;
&lt;p&gt;But, why wouldn&amp;#8217;t I have expected something like that? If I could anticipate that they might abandon their stated values for predictable business reasons, why would I not expect that they might betray a set of values that were left unstated, ones that I merely assumed they held as a baseline of decency? Despite presenting themselves as a hacker-friendly, values-forward company doing things in new and different ways in order to tackle an intractable problem, at the end of the day they are still a &lt;span class="caps"&gt;VC&lt;/span&gt; funded Silicon Valley startup. It is a joke at this point how many such entities have abandoned their stated principles or otherwise betrayed their customers and the public in the pursuit of growth, or for political expedience, and many more whose principles were always obvious lies, or based on disingenuous right-wing distortions of values like &amp;#8220;free&amp;nbsp;speech&amp;#8221;.&lt;/p&gt;
&lt;p&gt;&lt;img alt="The &amp;quot;I don't want to play with you any more&amp;quot; meme, with Google and &amp;quot;Don't be evil&amp;quot;" src="https://blog.hyperlinkyourheart.com/images/values/google.jpg" title="The most famous and blatant example"&gt;&lt;/p&gt;
&lt;h2&gt;What &lt;em&gt;Are&lt;/em&gt; Corporate Values&amp;nbsp;Even?&lt;/h2&gt;
&lt;p&gt;While there are sometimes better or worse choices amongst products and services, from an ethical perspective, it is not in the nature of the companies that produce them under the capitalist system to put principles or values above the accumulation of&amp;nbsp;capital.&lt;/p&gt;
&lt;p&gt;&lt;img alt="I don't know the name of this meme, but it's the one with the guy about to smash a card onto the table in a card game. He's labeled &amp;quot;Yo&amp;quot; (me), the table is &amp;quot;Una conversacion casual&amp;quot; (a casual conversation), and his card is &amp;quot;El problema es el capitalismo&amp;quot; (the problem is capitalism)" src="https://blog.hyperlinkyourheart.com/images/values/el-problema.jpg" title="I just can't help myself."&gt;&lt;/p&gt;
&lt;p&gt;One obvious problem with such organisations is of course that they are profit-seeking, and subject to the whims of the market. If a strategy isn&amp;#8217;t working (i.e. is not resulting in growth), a company that doesn&amp;#8217;t adapt will stagnate or wither away to nothing as their competitors crowd them out of the market. If a &lt;em&gt;principle&lt;/em&gt; is an obstacle to that adaptation, then it is likely to be discarded, because holding to it would be an existential threat. This is the main source of the risks I identified above. If they manage to survive and grow to the point where they are dominating or even monopolising their industry, it is likely that any putative values will have long been abandoned. And if they are bought out by a larger company, there is no reason to expect that their values will survive the&amp;nbsp;transition.&lt;/p&gt;
&lt;p&gt;Another problem is that they are hierarchical; private tyrranies in fact. The feelings, opinions, obsessions and values of their owners, &lt;span class="caps"&gt;CEO&lt;/span&gt; and other officers, carry much more weight than anybody working within them, and certainly more than their customers. Sometimes this can work contrary to a company&amp;#8217;s need for growth. A principled owner/&lt;span class="caps"&gt;CEO&lt;/span&gt; could &lt;em&gt;potentially&lt;/em&gt; stick with lower-growth strategies as long as there is nobody in a position to fire them - though I wouldn&amp;#8217;t expect such a situation to last indefinitely. An unhinged &lt;span class="caps"&gt;CEO&lt;/span&gt; on a bender could help elect a fascist who is hostile to his company&amp;#8217;s interests, confident that his cult of personality will protect him from the consequences. Or in a somewhat less extreme scenario, the &lt;span class="caps"&gt;CEO&lt;/span&gt; might just want to help out some of his problematic fave open source developers, and not want to think about why that is a problem for some people, and there&amp;#8217;s nothing that anybody can really do about it except make angry noises online and refuse to buy any more of the company&amp;#8217;s products. We can&amp;#8217;t fork a laptop manufacturer like we can an open source project, unfortunately (and forking a major open source project isn&amp;#8217;t exactly trivial&amp;nbsp;either).&lt;/p&gt;
&lt;p&gt;These people exist in completely different milieu than the rest of us, responsible for millions or billions of &lt;code&gt;${currencyUnits}&lt;/code&gt; worth of undemocratically allocated capital. Our concerns about the swelling tide of racist and queerphobic rhetoric and attacks around the world are abstract to them, mere differences of opinion. They and their peers are concerned about real things, like access to cheap skilled labour, regulations that might reduce their power or increase their costs, and taxes that might shave a few million off their accumulated or potential billions. They&amp;#8217;re more likely to see it as prudent to appease fascist movements than to oppose them, once they come to power. Violent street gangs, uniformed or otherwise, don&amp;#8217;t really affect them. The policy whims of a mercurial leader might. And anyway, if they play their cards right they might get to be the Hugo Boss or &lt;span class="caps"&gt;IBM&lt;/span&gt; of the 21st century. Or get their own government&amp;nbsp;department. &lt;/p&gt;
&lt;p&gt;In the world of capitalist tech companies, stated principles and values are best understood as marketing: nice, cheap, non-binding promises that bring certain demographics to the trough, or make people feel good about their purchases, that they are making responsible choices or contributing to some improvement to the world. It certainly worked well enough on me, for Framework. They can hold to them, or pretend to, as long as they are useful, and abandon them when they become a burden. Their real values emerge from the inhuman machinery of the capitalist system, and the class interests of men twisted by privilege and&amp;nbsp;power.&lt;/p&gt;
&lt;h2&gt;What is to be&amp;nbsp;done?&lt;/h2&gt;
&lt;p&gt;The laptop market is the way it is because it is more profitable to manufacture disposable products and sell replacements for them in the future than it is to make repairable products that you may sell fewer of. Good intentions and green consumerism is unlikely to change that, as admirable as the attempt may&amp;nbsp;seem.&lt;/p&gt;
&lt;p&gt;Not to be all whatabouty, but certainly it is absurd to say that it is &lt;em&gt;bad&lt;/em&gt; to buy a Framework, and &lt;em&gt;good&lt;/em&gt; to buy a laptop from a company with a dismal track record on repairability, which is currently also sucking up to a fascist administration. All for-profit tech companies are somewhere on a spectrum from bad to worse, and there is no ethical consumption under&amp;nbsp;capitalism.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Photo of Tim Apple presenting that engraved glass disc with the golden base to Donald Trump. The disc is foregrounded, with the two men out of focus in the background." src="https://blog.hyperlinkyourheart.com/images/values/timapple.jpg" title="Orange man bad. Apple man also bad."&gt;&lt;/p&gt;
&lt;p&gt;No doubt there are many regulations that could be put in place to increase repairability of all sorts of devices and reduce other sources of e-waste. Stuff along the lines of the &lt;span class="caps"&gt;EU&lt;/span&gt;&amp;#8217;s regulations on &lt;a href="https://eur-lex.europa.eu/eli/reg/2023/1670/oj" title="EU regulation on phone repairability."&gt;phone repairability&lt;/a&gt;, and the &lt;a href="https://commission.europa.eu/news-and-media/news/eu-common-charger-rules-power-all-your-devices-single-charger-2024-12-28_en" title="European Commission article about the common charger rules."&gt;common charger rules&lt;/a&gt; that came into force for most devices in December 2024, and will apply to laptops from April 2026. We should be dictating to the industry the &lt;a href="https://repair.eu/whats-my-right-to-repair/" title="Right to Repair EU's chart of right to repair legislation by device type."&gt;standards of repairability&lt;/a&gt; and sustainability that they have to meet, not hoping for plucky American startups to innovate a new set of norms - and we should be ruthless in our&amp;nbsp;demands.&lt;/p&gt;
&lt;p&gt;In the meantime, and always, we should take the commitments of for-profit companies with a grain of salt, and not allow their products to define us. I maintain that the tech industry will not reflect our values until we own everything, from the chip fabs to the stars, and the profit motive is a thing of the&amp;nbsp;past.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Pixel art portrait of James Connolly" src="https://blog.hyperlinkyourheart.com/images/values/jimmy.png" title="To adapt a phrase..."&gt;&lt;/p&gt;</content><category term="Technology"></category><category term="framework"></category><category term="capitalism"></category><category term="fascism"></category><category term="socialism"></category><category term="politics"></category></entry><entry><title>The Ministry for the Future</title><link href="https://blog.hyperlinkyourheart.com/ministry-for-the-future.html" rel="alternate"></link><published>2025-09-13T17:13:00+02:00</published><updated>2025-09-13T17:13:00+02:00</updated><author><name>Kevin Houlihan</name></author><id>tag:blog.hyperlinkyourheart.com,2025-09-13:/ministry-for-the-future.html</id><summary type="html">&lt;p&gt;Longtermism as it should&amp;nbsp;be.&lt;/p&gt;</summary><content type="html">&lt;p&gt;The first chapter of this book is one of the most brutal and visceral things I have ever read. It describes, from the perspective of the sole survivor in a particular town, a devastating climate change induced heatwave in India that kills 20 million, and in the process, I think, captures the prevailing mood amongst those who understand the urgency of the situation we&amp;nbsp;face.&lt;/p&gt;
&lt;p&gt;After that it calms down a bit, and has a quite unusual narrative structure. Some chapters are like snippets of a political manifesto, others are vignettes about unidentified characters and groups and how they are responding to the climate crisis. The main through-line concerns the head of a &lt;span class="caps"&gt;UN&lt;/span&gt; agency, the titular Ministry for the Future, the efforts of the agency, and her interactions with the sole survivor I mentioned above, who was radicalised by his&amp;nbsp;experience.&lt;/p&gt;
&lt;p&gt;It is more a book about concepts than it is about action or plot. The main concept it explores is what we could probably call Longtermism, if that name hadn&amp;#8217;t been hijacked by transhumanist tech-bros to justify their disregard of people living today, supposedly in favour of the interests of billions of hypothetical future people, but really in their own selfish interests. In the book, the concept (though unnamed) concerns future generations whose existence is threatened by our actions today. Their interests are defended by the&amp;nbsp;Ministry.&lt;/p&gt;
&lt;p&gt;Does the threat represented by climate change to people living today, and the existence of future generations, justify violence? Does it justify geoengineering? This book says yes, and yes. The mass deaths of the heatwave spur radical action on multiple fronts, including a grassroots campaign of bombings of polluting modes of transportation like airplanes and container ships so intense that it makes them economically unviable, assassinations of polluters, black ops campaigns of violence and intimidation by the Ministry, and several massive geoengineering projects to slow sea level rise, reductions in the Earth&amp;#8217;s albedo, and other things. While we see a lot of the geoengineering stuff up close, it is one of the books weak points that the violence mostly happens in the background, and is all a little too neat and efficient. Even as it threatens the main character&amp;#8217;s life the players on both sides remain unidentified, and is mostly used as a pretext to justify radical, but technocratic, reforms to the financial system. I think it would have been better if we were down in the trenches a bit more, with a less abstract villain than the agents of unidentified reactionary fossil capitalists and&amp;nbsp;elites.&lt;/p&gt;
&lt;p&gt;Another problem I have with this book is that it is way too enthusiastic about the liberatory potential of blockchain technology. At least it is clear that that potential lies in it being a tool of surveillance to prevent the wealthy from hiding their money and doing whatever they want with it, rather then the sort of digital goldbuggery, private money nonsense that most proponents embrace. But, given what we have seen so far of how it is used in the real world, I have my doubts that it will ever be a tool for anything other than enabling scams, corruption, and wealth&amp;nbsp;concentration.&lt;/p&gt;
&lt;p&gt;Overall a very interesting read that is both an intimate exploration of the trauma and anxiety caused by climate change, and a detached and ultimately optimistic analysis of some of the political and technological possibilities for addressing&amp;nbsp;it.&lt;/p&gt;
&lt;h2&gt;More&amp;nbsp;Books&lt;/h2&gt;
&lt;p&gt;I have an &lt;a href="https://bookwyrm.social/user/hyperlinkyourheart"&gt;account on bookwyrm.social&lt;/a&gt; where I post about what I&amp;#8217;m reading and sometimes reviews if I feel I have enough to say about a book (including a version of this review), if you want to follow me&amp;nbsp;there. &lt;/p&gt;</content><category term="Books"></category><category term="climate-change"></category><category term="fiction"></category><category term="capitalism"></category><category term="politics"></category></entry><entry><title>The Power of Breaks</title><link href="https://blog.hyperlinkyourheart.com/breaks.html" rel="alternate"></link><published>2025-08-08T14:10:00+02:00</published><updated>2025-08-08T14:10:00+02:00</updated><author><name>Kevin Houlihan</name></author><id>tag:blog.hyperlinkyourheart.com,2025-08-08:/breaks.html</id><summary type="html">&lt;p&gt;Sometimes taking a break makes the solution to a problem obvious when you return to&amp;nbsp;it.&lt;/p&gt;</summary><content type="html">&lt;p&gt;A few months ago I was stuck on a particularly frustrating problem in the development of &lt;a href="https://github.com/khoulihan/digression" title="Digression repository on GitHub."&gt;Digression&lt;/a&gt;, my dialogue graph editor for &lt;a href="https://godotengine.org/" title="The game engine you waited for."&gt;Godot&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I had recently switched it to a main screen editor, as discussed in my &lt;a href="https://blog.hyperlinkyourheart.com/lovely.html" title="Post-mortem of my Ludum Dare entry Lovely, Dark and Deep."&gt;post-mortem of my last Ludum Dare entry&lt;/a&gt;, and added a list of open graphs, and another of anchor points in the currently edited graph, similar to the scripts and methods list in the built-in script editor. Basically I have decided to reproduce the &lt;span class="caps"&gt;UI&lt;/span&gt; conventions of the script editor where they are at all&amp;nbsp;applicable.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/khoulihan/digression" title="Digression repository on GitHub."&gt;&lt;img alt="Screenshot of the graph editor main screen described above." src="https://blog.hyperlinkyourheart.com/images/breaks/digression.png" title="It's nearly looking like a built-in part of the editor now."&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;This seemed fine initally, but as I got stuck into a big refactor of how the underlying resources are updated by the &lt;span class="caps"&gt;GUI&lt;/span&gt;, and how and when those are saved, I noticed something really weird: sometimes when I pressed ctrl-s to save the scene and current graph, the editor would switch to one of the other open&amp;nbsp;graphs!&lt;/p&gt;
&lt;p&gt;I think I must have spent two weeks on and off trying to figure out this problem. The code was absolutely littered with debug log lines trying to trace the sequence of events triggered by the save, but there didn&amp;#8217;t seem to be any connection between the code doing the saving, or the code handling key input, and the code responsible for switching between graphs. Eventually I gave up in frustration, and decided to &lt;a href="https://blog.hyperlinkyourheart.com/coming-down.html" title="Coming Down art post."&gt;do some art&lt;/a&gt; in my spare time instead. Then I played some video&amp;nbsp;games.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Marketing image for the game &amp;quot;The Alters&amp;quot;, showing the main character and his clones in a huddle on the right, with the name of the game on the left." src="https://blog.hyperlinkyourheart.com/images/breaks/alters.jpg" title="It's really good."&gt;&lt;/p&gt;
&lt;p&gt;Well, when I finally came back to it, I figured out what was going on within five minutes. First I noticed that the graph that was being switched to was always one with a name starting with the letter s, and if there wasn&amp;#8217;t one starting with an s, then nothing happened. I tried other ctrl key combos and those switched to graphs starting with the corresponding letter, if there was one. It wasn&amp;#8217;t anything to do with saving, or how I&amp;#8217;d implemented it, or anything to do with my code at all, it was just a &lt;a href="https://github.com/godotengine/godot/issues/109274" title="Issue on the Godot issue tracker for the described bug."&gt;bug in the search feature of the ItemList node&lt;/a&gt;!&lt;/p&gt;
&lt;p&gt;When I was deep into the development I had a very blinkered view of what was going on: I&amp;#8217;m working on saving, it happens when I save, it must have something to do with that, and it must be due to changes I&amp;#8217;ve made. A little break from it gave me enough perspective to see that none of that was the case, and there was ample evidence demonstrating&amp;nbsp;that.&lt;/p&gt;
&lt;p&gt;This of course isn&amp;#8217;t the first time in my life, in programming or other endeavours, that taking a break has helped me solve a problem or even develop skills. I often find that taking a break from art results in a seemingly huge improvement in my skills when I return to it. In that case I think it is down to being more relaxed and putting less pressure on myself when starting a new piece after a break than when I have recently finished a piece that I feel I need to improve&amp;nbsp;upon.&lt;/p&gt;
&lt;p&gt;In conclusion: breaks good. Take a step back&amp;nbsp;sometimes.&lt;/p&gt;</content><category term="Game Development"></category><category term="godot"></category><category term="life"></category><category term="self-care"></category></entry><entry><title>Coming Down</title><link href="https://blog.hyperlinkyourheart.com/coming-down.html" rel="alternate"></link><published>2025-07-08T11:59:00+02:00</published><updated>2025-07-08T11:59:00+02:00</updated><author><name>Kevin Houlihan</name></author><id>tag:blog.hyperlinkyourheart.com,2025-07-08:/coming-down.html</id><summary type="html">&lt;p&gt;A pixel art portrait of a woman, loosely inspired by the Chemical Brothers&amp;#8217; song &amp;#8220;Where Do I&amp;nbsp;Begin&amp;#8221;.&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;a href="https://portfolio.hyperlinkyourheart.com/coming-down.html"&gt;&lt;img alt="A pixel art painting of a woman's face, close up, in green hues. There are stars in the background to her right and a hint of some in her eye." src="https://blog.hyperlinkyourheart.com/images/coming-down/ComingDown1_x2.png" title="Coming Down"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Something I&amp;#8217;ve had in my mind for a while is to do something based on the Chemical Brothers&amp;#8217; song &amp;#8220;Where Do I Begin&amp;#8221;, which I think captures well the feeling of coming down after a trip; the mix of awe and confusion and the tenuous connection to your sense of&amp;nbsp;self.&lt;/p&gt;
&lt;p&gt;So I set out to do that, intending it to feature a woman with a coffee cup, like in the song&amp;#8230; and somehow got diverted into this close-up portrait. I think I had planned something like &lt;a href="https://blog.hyperlinkyourheart.com/cosmic-eye.html"&gt;Cosmic Eye&lt;/a&gt;, where she would have a universe visible in her pupil, but she wasn&amp;#8217;t really at the necessary scale for that to&amp;nbsp;work.&lt;/p&gt;
&lt;p&gt;I further intended to animate the colours, and shift between different palettes in a sort of pulsing manner, but that wasn&amp;#8217;t working out the way I planned, so I abandoned&amp;nbsp;it.&lt;/p&gt;
&lt;p&gt;I guess the most notable feature of this piece is the dithering style, which is sort of rough and incorporates various shapes. I&amp;#8217;ve never tried this style&amp;nbsp;before.&lt;/p&gt;
&lt;p&gt;I might need to try this again and stick closer to my original&amp;nbsp;vision&amp;#8230;&lt;/p&gt;
&lt;h2&gt;Timelapse&lt;/h2&gt;
&lt;p&gt;I used the recorder feature of &lt;a href="https://github.com/Orama-Interactive/Pixelorama" title="Pixelorama (Github)"&gt;Pixelorama&lt;/a&gt; to capture this timelapse, in the mode that only captures the canvas. I like that the result does not hop around erratically like my previous timelapse, but it does get a bit hard to follow what is actually being worked on at some points when it gets into the finer details, especially the&amp;nbsp;anti-aliasing.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=XP-Zx64bAqU"&gt;&lt;img alt="Coming Down Timelapse" src="https://img.youtube.com/vi/XP-Zx64bAqU/0.jpg"&gt;&lt;/a&gt;&lt;/p&gt;</content><category term="Art"></category><category term="pixelart"></category><category term="portrait"></category><category term="music"></category><category term="psychedelia"></category></entry><entry><title>Treat Mania</title><link href="https://blog.hyperlinkyourheart.com/treat-mania.html" rel="alternate"></link><published>2025-07-05T15:51:00+02:00</published><updated>2025-07-05T15:51:00+02:00</updated><author><name>Kevin Houlihan</name></author><id>tag:blog.hyperlinkyourheart.com,2025-07-05:/treat-mania.html</id><summary type="html">&lt;p&gt;I&amp;#8217;ve been on a diet since last October and my desire for treats is slowly taking over my&amp;nbsp;mind.&lt;/p&gt;</summary><content type="html">&lt;p&gt;I&amp;#8217;ve been on a diet since October last year and I&amp;#8217;ve lost nearly 14kg so far. Only 6kg to go to my target weight. So, pretty successful. My wife started a few months before me and convinced me to join her, and it has actually been quite&amp;nbsp;fun.&lt;/p&gt;
&lt;p&gt;&lt;img alt="A chart of my weight since the start of my diet - starts at approx 85kg, now at 71.3kg." src="https://blog.hyperlinkyourheart.com/images/treat-mania/weight.jpg" title="Spot the holidays."&gt;&lt;/p&gt;
&lt;p&gt;It&amp;#8217;s not anything complicated, just tracking how many calories we consume and keeping it low for 5 days a week, and then eating &amp;#8220;normally&amp;#8221; on Wednesdays and Saturdays. I should say it&amp;#8217;s especially uncomplicated for me because my wife does all the cooking 😬 The high days are supposed to keep our metabolisms relatively high. I don&amp;#8217;t know if it really works that way, but they also have a secondary psychological effect: it makes it much easier to eat less on the low days when I know I am at most two days from being able to eat whatever I want - perhaps something I&amp;#8217;ve specifically been&amp;nbsp;craving.&lt;/p&gt;
&lt;p&gt;At least&amp;#8230; that&amp;#8217;s how it worked at the beginning. As time has gone on I have found myself eating more and more garbage on the high days. I never really even used to eat that many treats or think about them that much, but now those two days a week are my only opportunity to have some, and I have to fit in as much as possible. It started with a Snickers bar after lunch, but otherwise normal, healthy food, and now I have a cupboard groaning with different types of chocolate, marshmallows, jellies, crisps, jellybeans, sweet-chili pistachios for some reason (the reason is that I have lost my mind). Every time we go to Aldi I find some new thing to add to my collection, and usually several&amp;nbsp;donuts.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Photo of a bag of sweet chili pistachios from Lidl? Aldi? I can't remember." src="https://blog.hyperlinkyourheart.com/images/treat-mania/pistachios.jpg" title="What is this foolishness?"&gt;&lt;/p&gt;
&lt;p&gt;I have anxiety about my treat cupboard and I spend the rest of the week thinking about how I&amp;#8217;m going to gorge myself on Wednesday and Saturday. Because the amount I can eat is still quite restricted even on those days, I never actually manage to finish anything before moving on to the new sweetness. I have half a bag of vegan marshmallows in there for the last six months that I never touch because a marshmallow is nearly a hundred calories, and if I have one that&amp;#8217;s 30g of jellybeans I can&amp;#8217;t eat. There&amp;#8217;s things in there I&amp;#8217;ve never even tried, because I can&amp;#8217;t risk wasting my calorie budget on something I might not like, and miss out on some essential high-value treat (whatever that happens to be on any given day). If I skip lunch can I have a second Snickers as well as 100g of ice-cream? This is the type of question that consumes&amp;nbsp;me.&lt;/p&gt;
&lt;p&gt;So, that doesn&amp;#8217;t seem like a particularly healthy relationship to food. But oh well, only 6kg to go, then I&amp;#8217;m going to make a giant lasagna and eat that for three days in a row, and then hopefully go back to treat moderation instead of treat&amp;nbsp;mania.&lt;/p&gt;</content><category term="Life"></category><category term="diet"></category><category term="food"></category></entry><entry><title>Lovely, Dark and Deep</title><link href="https://blog.hyperlinkyourheart.com/lovely.html" rel="alternate"></link><published>2025-06-14T18:28:00+02:00</published><updated>2025-06-14T18:28:00+02:00</updated><author><name>Kevin Houlihan</name></author><id>tag:blog.hyperlinkyourheart.com,2025-06-14:/lovely.html</id><summary type="html">&lt;p&gt;Post-mortem and results for my Ludum Dare 57&amp;nbsp;entry.&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;a href="https://hyperlinkyourheart.itch.io/lovely-dark-and-deep" title="itch.io page for the game"&gt;&lt;img alt="Pixel art of a forest with a man on a bicycle in the foreground, and the text &amp;quot;Lovely, Dark and Deep&amp;quot;" src="https://blog.hyperlinkyourheart.com/images/lovely-dark-and-deep/cover_image.png" title="Lovely, Dark and Deep"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Back in April I took part in Ludum Dare 57, which had the theme &amp;#8220;Depths&amp;#8221;. As usual I was eager to take &lt;a href="https://github.com/khoulihan/digression" title="Digression repository on GitHub"&gt;Digression&lt;/a&gt; for a spin and see how much it had improved since its last outing for &lt;a href="https://hyperlinkyourheart.itch.io/out-of-gas" title="Out of Gas on itch.io"&gt;Ludum Dare 48&lt;/a&gt;, and this time I managed to come up with something that allowed me to do that. Coincidentally, the theme for &lt;span class="caps"&gt;LD48&lt;/span&gt; was almost the same (&amp;#8220;Deeper and Deeper&amp;#8221;), and I ended up making a somewhat similar game in some&amp;nbsp;respects!&lt;/p&gt;
&lt;h2&gt;The&amp;nbsp;Concept&lt;/h2&gt;
&lt;p&gt;The initial concept was one I&amp;#8217;ve had kicking around for a while, of a post-apocalyptic trading game with two phases to the gameplay - one where you travel between outposts by car, possibly having to engage in combat or other encounters, and another at outposts where you have to trade and maintain your car. I think of it as terrestrial &lt;a href="https://en.wikipedia.org/wiki/Escape_Velocity_(video_game)" title="Wikipedia entry for the game Escape Velocity"&gt;Escape Velocity&lt;/a&gt;, though the travel would undoubtedly be much simplified for a jam&amp;nbsp;game.&lt;/p&gt;
&lt;p&gt;Of course none of the elements of that idea are set in stone. I usually think of it as being set in a desert (the ol&amp;#8217; Mad Max influence I suppose), but in this case I wasn&amp;#8217;t sure if deserts &lt;em&gt;have&lt;/em&gt; depths? So that was out. I considered setting it in deep space instead (some sort of &lt;em&gt;non-terrestrial&lt;/em&gt; Escape Velocity, I suppose? What a notion), but that seemed too close to the &lt;a href="https://hyperlinkyourheart.itch.io/out-of-gas" title="Out of Gas on itch.io"&gt;game I made for Ludum Dare 48&lt;/a&gt; already. And so it became deep forest, and then the car became a bicycle because that seemed more&amp;nbsp;foresty.&lt;/p&gt;
&lt;p&gt;Most of the rest of the originally envisioned gameplay mechanics fell away as I tried to scope it to the 72 hours available. The travelling became just an opportunity for a bit of dialogue, the vehicle maintenance became a simple stat alongside others for the player, and the trading disappeared entirely. In an effort to introduce some gameplay beyond just engaging in dialogue, I added in a hand of randomly drawn cards at each location and a limited number of action points for their use. I think I had in mind the dice rolls in &lt;a href="https://citizensleeper.com/" title="Website of the Citizen Sleeper series of games"&gt;Citizen Sleeper&lt;/a&gt; when I came up with this, but I couldn&amp;#8217;t really remember how they worked in that&amp;nbsp;game.&lt;/p&gt;
&lt;p&gt;Considering it has been several months since the jam I don&amp;#8217;t think I could give you a blow by blow even if I wanted to, so I&amp;#8217;m just going to talk a bit about what worked and what&amp;nbsp;didn&amp;#8217;t.&lt;/p&gt;
&lt;h2&gt;What&amp;nbsp;Worked&lt;/h2&gt;
&lt;h3&gt;Digression&lt;/h3&gt;
&lt;p&gt;Digression worked really well for the most part. I think I encountered one bug, but I was able to work around it. I was able to churn out dialogue really quickly for the encounters and travel, and it was relatively easy to implement changes to the game state during dialogue as well (giving items and buffs and the like) - though that aspect of it could have been better, as I discuss&amp;nbsp;below.&lt;/p&gt;
&lt;h3&gt;Use of&amp;nbsp;Resources&lt;/h3&gt;
&lt;p&gt;I used &lt;code&gt;Resource&lt;/code&gt; types for all the items, buffs, curses, character classes etc. Although they were a bit of a mess and included some stuff that didn&amp;#8217;t end up getting used, it really paid off to work with resources right from the beginning. Often in the past I would implement something like the player&amp;#8217;s gun, for example, as just a scene or part of a scene, with the intention to rework it later to allow different guns defined by resources. And of course in a jam that never happens as time runs out and I get fatigued - so there ends up being only one gun, one pickup&amp;nbsp;etc.&lt;/p&gt;
&lt;p&gt;Probably the most obvious effect this had in this game is the character class selection. It doesn&amp;#8217;t add all that much, but I think it is a neat thing to have in a jam game and it gives the impression of&amp;nbsp;depth.&lt;/p&gt;
&lt;p&gt;So, big thumbs up to doing things the right way from the&amp;nbsp;beginning.&lt;/p&gt;
&lt;h3&gt;Audio&lt;/h3&gt;
&lt;p&gt;I used old reliable &lt;a href="https://lmms.io/" title="Website of the LMMS DAW"&gt;&lt;span class="caps"&gt;LMMS&lt;/span&gt;&lt;/a&gt; for the music this time around. Last time I tried some new tools and had a really bad time. I&amp;#8217;m really proud of what I made for this game, it had the exact spooky, mysterious quality I was going for. I don&amp;#8217;t think &lt;a href="https://www.beepbox.co" title="Beepbox - an online chiptune music tool"&gt;beepbox&lt;/a&gt; would have cut it this&amp;nbsp;time.&lt;/p&gt;
&lt;p&gt;The game didn&amp;#8217;t call for many sound effects, but the ones that are there, aside from the &lt;span class="caps"&gt;UI&lt;/span&gt;, are foley - always fun to do that and I think they work pretty&amp;nbsp;well.&lt;/p&gt;
&lt;h3&gt;Art&lt;/h3&gt;
&lt;p&gt;What can I say, I think I did a good job on the art. That&amp;#8217;s always been my strength anyway so big&amp;nbsp;woop.&lt;/p&gt;
&lt;p&gt;I am still using &lt;a href="https://github.com/Orama-Interactive/Pixelorama" title="Pixelorama on GitHub"&gt;Pixelorama&lt;/a&gt; and there are still a few things about it that I find grating, but I have gotten used to it enough that it doesn&amp;#8217;t get in my way. It was better suited to this game than my last one because most of the art was large background pieces rather than&amp;nbsp;tiles.&lt;/p&gt;
&lt;p&gt;Something that&amp;#8217;s kinda new is that I did lot of animation using tweens. Little things like the the back and forth motion of the bicycle and how it enters and exits the screen, and the distribution and flipping of the cards, were entirely done with tweens. They took only minutes to implement and I think they look great, and add so much to the aesthetic of the game. I only had to actually animate one thing by hand, the&amp;nbsp;pedalling.&lt;/p&gt;
&lt;h3&gt;The Humour,&amp;nbsp;apparently&lt;/h3&gt;
&lt;p&gt;That Sully, what a&amp;nbsp;card.&lt;/p&gt;
&lt;h2&gt;What&amp;nbsp;Didn&amp;#8217;t&lt;/h2&gt;
&lt;h3&gt;Digression&lt;/h3&gt;
&lt;p&gt;Though it certainly didn&amp;#8217;t let me down, I definitely noticed a few pain points in actually using it for a&amp;nbsp;game.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;It was originally a main-screen editor, and at some point I decided it should be in the bottom dock - probably because Godot didn&amp;#8217;t provide an official way to add a main screen plugin and you had to sort of hack it in. However it requires a lot of space to use properly, and having it in the bottom dock was an impediment to that. I have moved it back to the main screen since the&amp;nbsp;jam.&lt;/li&gt;
&lt;li&gt;Having to manually manage the size of dialogue nodes was a hassle. I have since updated them to be resizeable width-wise, but to grow with the text length-wise. Much nicer to&amp;nbsp;use.&lt;/li&gt;
&lt;li&gt;Similarly, it was kind of a pain scrolling up and down a node when there was a long section of text. I have since partially implemented a mechanism to allow the dialogue node to be &amp;#8220;maximised&amp;#8221; when you want to focus on editing, and I will be doing the same for the choice&amp;nbsp;node.&lt;/li&gt;
&lt;li&gt;The lack of an open graphs list made it a bit awkward to move back and forth between graphs, as I had to find them in a scene or the filesystem every time. I have since added an open graphs list, as well as an anchors list for navigating around a&amp;nbsp;graph.&lt;/li&gt;
&lt;li&gt;The two different signals for dialogue and choice dialogue struck me as a little awkward in retrospect. I will be consolidating them I&amp;nbsp;think.&lt;/li&gt;
&lt;li&gt;There are a few problems with the variable management system. One is that they lacked defaults - I have added these since. Another is that the variables didn&amp;#8217;t mesh well with the overall game, i.e. game state and dialogue state felt like two separate things that I had to bridge. I&amp;#8217;m not sure how I am going to address that&amp;nbsp;yet.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;Gameplay&lt;/h3&gt;
&lt;p&gt;The gameplay was pretty awful. I clearly do not really know how card games work. Limiting the player&amp;#8217;s actions to a random selection of cards just resulted in frustration, especially when it was not obvious that some actions required items, and when their use of the cards was also constrained by action&amp;nbsp;points.&lt;/p&gt;
&lt;p&gt;The stats generally degraded too slowly as well and there were unclear interdependencies between them - your health doesn&amp;#8217;t degrade until you exhaust your food or water stats&amp;nbsp;completely.&lt;/p&gt;
&lt;p&gt;The encounters were fun, I thought, but there were too few of them and they were too random - you would see the same ones over and over sometimes. Too many of them had disappointing outcomes, and if you didn&amp;#8217;t get the right cards there was nothing to&amp;nbsp;do.&lt;/p&gt;
&lt;p&gt;I think the random encounters worked a bit better in &lt;a href="https://hyperlinkyourheart.itch.io/out-of-gas" title="Out of Gas on itch.io"&gt;Out of Gas&lt;/a&gt; for two reasons: most encounters involved combat, so that is just inherently more exciting - and the fact that you were fleeing something, and could be killed, was slightly more compelling than a set of depleting&amp;nbsp;stats.&lt;/p&gt;
&lt;p&gt;Overall, the randomness just left the player with little sense of agency. I would have been better off dropping the gameplay altogether and just making it a short visual novel, with agency expressed only through dialogue choices. Though I&amp;#8217;m not sure I could write a compelling story for a visual novel in such a short timespan&amp;nbsp;either&amp;#8230;&lt;/p&gt;
&lt;h3&gt;Bugs&lt;/h3&gt;
&lt;p&gt;I introduced a bug at the last minute that prevented the game from ending when your health or vehicle state reached zero lol,&amp;nbsp;lmao.&lt;/p&gt;
&lt;h2&gt;Results&lt;/h2&gt;
&lt;p&gt;&lt;img alt="Results table - 257th overall, 600th in Fun, 256th in Innovation, 446th in Theme, 80th in Graphics, 49th in Audio, 48th in Humour, 70th in Mood" src="https://blog.hyperlinkyourheart.com/images/lovely-dark-and-deep/results.png" title="Not too bad. Could be better."&gt;&lt;/p&gt;
&lt;p&gt;Much better than my last two outings, and some good ratings in several categories, but definitely let down overall by the gameplay. I&amp;#8217;m a bit disappointed by the mood rating as I felt I nailed that, but it is a very subjective one of course - and it&amp;#8217;s still pretty&amp;nbsp;good!&lt;/p&gt;
&lt;p&gt;Some&amp;nbsp;notables:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;My highest ever rating, and placing, in humour (please&amp;nbsp;clap).&lt;/li&gt;
&lt;li&gt;Highest ever audio rating (by a hair), and&amp;nbsp;placing.&lt;/li&gt;
&lt;li&gt;First time being in the top 100 in four categories as a solo&amp;nbsp;dev.&lt;/li&gt;
&lt;li&gt;First time being in the top 50 in two categories. Third time being in the top 50 in any&amp;nbsp;category.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Second lowest&lt;/em&gt; rating in fun - only my Ludum Dare 38 entry, which was only the opening scene of an adventure game and had no gameplay, was&amp;nbsp;lower!&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img alt="Graph of audio placings" src="https://blog.hyperlinkyourheart.com/images/lovely-dark-and-deep/audioplacings.png"&gt;
&lt;img alt="Graph of humour ratings" src="https://blog.hyperlinkyourheart.com/images/lovely-dark-and-deep/humourratings.png"&gt;&lt;/p&gt;
&lt;h2&gt;Post-Jam&lt;/h2&gt;
&lt;p&gt;Nah.&lt;/p&gt;
&lt;h2&gt;Future&amp;nbsp;Developments&lt;/h2&gt;
&lt;p&gt;I was inspired to work on some sweeping improvements to Digression and have made a lot of progress, but there is also a lot of stuff broken and partially completed now. I will post about the changes in more depth when they are&amp;nbsp;done.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Screenshot of dialogue and choice nodes in Digression" src="https://blog.hyperlinkyourheart.com/images/lovely-dark-and-deep/digressioncurrentstate.png" title="An example of some of the changes I've made"&gt;&lt;/p&gt;
&lt;p&gt;I also created a set of placeholder graphics to use in future jams. I used the default Godot robot sprite in this game, but it is not ideal in some ways because everything ends up looking the same, and when you put in the actual art you have to undo any scaling that was done to make the placeholder the correct&amp;nbsp;size.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Screenshot of the game in early development, with the Godot logo everywhere" src="https://blog.hyperlinkyourheart.com/images/lovely-dark-and-deep/placeholders.png" title="Looks like Godot is already here"&gt;&lt;/p&gt;</content><category term="Game Development"></category><category term="ludum-dare"></category><category term="godot"></category><category term="godot4"></category><category term="pixelart"></category><category term="music"></category></entry><entry><title>Big Lick</title><link href="https://blog.hyperlinkyourheart.com/big-lick.html" rel="alternate"></link><published>2025-06-12T15:36:00+02:00</published><updated>2025-06-12T15:36:00+02:00</updated><author><name>Kevin Houlihan</name></author><id>tag:blog.hyperlinkyourheart.com,2025-06-12:/big-lick.html</id><summary type="html">&lt;p&gt;A pixel art painting I did of my brother-in-law and one of my parent&amp;#8217;s dogs. Also some chat about why I haven&amp;#8217;t done any art in&amp;nbsp;ages.&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;a href="https://portfolio.hyperlinkyourheart.com/big-lick.html"&gt;&lt;img alt="A pixel art painting of a reclining man being licked on the nose by a small dog." src="https://blog.hyperlinkyourheart.com/images/big-lick/BigLick06_x3.png" title="Big Lick"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Oh boy, it&amp;#8217;s been &lt;em&gt;a while&lt;/em&gt; since I posted any art! I think I was put off the whole endeavour back when NFTs were the big thing and a lot of artists I respected were showing their ass over them. I had also started a piece that was maybe a bit too complex for me, and I started to dread working on it, and to feel guilty about not working on it, and then I started thinking that art for its own sake was just a distraction from what I had actually set out to do, which was to make a videogame. I started putting a lot more time into developing my dialogue graph editor for &lt;a href="https://godotengine.org/" title="Godot Engine"&gt;Godot&lt;/a&gt; instead (since given the name &lt;a href="https://github.com/khoulihan/digression" title="Digression Dialogue Graph Editor (Github)"&gt;&amp;#8220;Digression&amp;#8221;&lt;/a&gt;), and a tiny bit into the mechanics of the game, and just didn&amp;#8217;t think about art for a&amp;nbsp;while.&lt;/p&gt;
&lt;p&gt;Since then of course, NFTs seem to have basically disappeared from the zeitgeist, their promises of vast riches and libertarian copyright enforcement for artists unfulfilled, and all the artists have been replaced by machines that can pull, on command, statistically likely representations of anything you can imagine out of the aggregate of all visual media on the Internet. As if it wasn&amp;#8217;t bad enough that a huge chunk of it was already owned by giant corporations, capital is now grinding up our culture into a meaningless, commodified slurry and feeding it back to us. The pretense of having the interests of artists at heart has been replaced by a naked, shameless grasping. And with governments falling over each other to attract &lt;span class="caps"&gt;AI&lt;/span&gt; investment, it seems unlikely that there is going to be any recourse for individual creators any time&amp;nbsp;soon.&lt;/p&gt;
&lt;p&gt;So I&amp;#8217;d like to say, then, that the reason I did this piece was that I&amp;#8217;m an obstinate luddite who refuses to accept that the age of human creativity is over, and that I had to do my part to help preserve it. But actually I just needed to do something for my sister&amp;#8217;s birthday, and it occurred to me that I&amp;#8217;d never done any pixel art for her even though she is someone who would probably appreciate it. And isn&amp;#8217;t that a fine, &lt;em&gt;human&lt;/em&gt; motivation for getting back into&amp;nbsp;art?&lt;/p&gt;
&lt;p&gt;She did appreciate it, btw. I&amp;#8217;m not sure she would have if I had &lt;em&gt;engineered her a prompt&lt;/em&gt;&amp;nbsp;instead.&lt;/p&gt;
&lt;h2&gt;Tools and&amp;nbsp;Process&lt;/h2&gt;
&lt;p&gt;I used to use &lt;a href="https://pyxeledit.com/" title="Pyxel Edit"&gt;Pyxel Edit&lt;/a&gt; for all my art, but it is proprietary and Windows only and I couldn&amp;#8217;t get it running in Wine since I switched to Fedora. So I have switched to &lt;a href="https://github.com/Orama-Interactive/Pixelorama" title="Pixelorama (Github)"&gt;Pixelorama&lt;/a&gt; instead, an open source pixel art paint program developed in Godot. There are a few things that Pyxel Edit did better, but Pixelorama is a very decent substitute. I like that you can put different tools on your left and right mouse buttons, though it tripped me up a lot initially. One thing I really miss is the ability to update a colour globally from the palette. It was great to be able to completely change direction with the colours without having to repaint anything. That was something I used to do quite often and at any stage of the process, sometimes even when a piece was otherwise finished. Maybe I will look into contributing to the project, since I am somewhat familiar with&amp;nbsp;Godot&amp;#8230;&lt;/p&gt;
&lt;p&gt;When I tried adding the reference image it just automatically put it under the canvas, which I wasn&amp;#8217;t expecting, but since it did I decided to just trace a rough outline of the figures for&amp;nbsp;expediency.&lt;/p&gt;
&lt;p&gt;Unusually for me I did a lot of anti-aliasing in this one, and I really liked how that turned out, especially on the face. More of that in the&amp;nbsp;future.&lt;/p&gt;
&lt;p&gt;Also unusually I did not pay much attention at all to how many colours I was using. There is relatively little dithering as a result, and what is there is mostly for texture rather than giving the appearance of additional colours. I usually like the challenge of keeping colour counts low, but it was nice to be more relaxed about it for a change - I ended up using 64, and it&amp;#8217;s not as if the resulting palette is general&amp;nbsp;purpose!&lt;/p&gt;
&lt;h2&gt;Timelapse&lt;/h2&gt;
&lt;p&gt;The timelapse for this one is a bit of a mess. My previous method of capturing them doesn&amp;#8217;t work in Wayland, and I overlooked a feature in Pixelorama that would have allowed me to evaluate the canvas without having to zoom in and out constantly. I&amp;#8217;ve since discovered that it has a recorder feature that can save a copy of the canvas after every change, so I will likely try that for my next piece and the timelapse should have a &lt;em&gt;very&lt;/em&gt; different&amp;nbsp;look&amp;#8230;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=FPiJwkzLr0U"&gt;&lt;img alt="Big Lick Timelapse" src="https://img.youtube.com/vi/FPiJwkzLr0U/0.jpg"&gt;&lt;/a&gt;&lt;/p&gt;</content><category term="Art"></category><category term="pixelart"></category><category term="portrait"></category><category term="dogs"></category><category term="nfts"></category><category term="politics"></category><category term="foss"></category><category term="capitalism"></category><category term="genai"></category></entry><entry><title>COVID Movies</title><link href="https://blog.hyperlinkyourheart.com/covid-movies.html" rel="alternate"></link><published>2024-06-30T13:30:00+02:00</published><updated>2024-06-30T13:30:00+02:00</updated><author><name>Kevin Houlihan</name></author><id>tag:blog.hyperlinkyourheart.com,2024-06-30:/covid-movies.html</id><summary type="html">&lt;p&gt;Movies I watched when I was&amp;nbsp;sick.&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;a name="top" /&gt;Well it happened - I finally caught the &amp;#8216;rona. Actually I think I had it quite early on, but it was a mild dose. This time I was basically insensible for two weeks and maybe even still not fully recovered two months later&lt;a href="https://blog.hyperlinkyourheart.com/covid-movies.html#note" title="Note"&gt;*&lt;/a&gt;. Quite the nasty&amp;nbsp;bug.&lt;/p&gt;
&lt;p&gt;To pass the time while I was couch-bound, I watched various comfort movies and movies I haven&amp;#8217;t seen since I was a&amp;nbsp;kid.&lt;/p&gt;
&lt;h2&gt;Eternal Sunshine of the Spotless&amp;nbsp;Mind&lt;/h2&gt;
&lt;p&gt;One of my all-time favourites. I appreciate it for it&amp;#8217;s examination of flawed characters and their very real and relatable relationship. I appreciate its message that even bad experiences sometimes have lessons to teach us, and that if we don&amp;#8217;t integrate them we will be doomed to repeat&amp;nbsp;them.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Screenshot from Eternal Sunshine of the Spotless Mind - Joel is in the scanning chair, on a New York street" src="https://blog.hyperlinkyourheart.com/images/covid-movies/whereismymind.jpg" title="Where is my mind?"&gt;&lt;/p&gt;
&lt;p&gt;Much of the movie resembles the recollection of memory, wandering back and forth through time, finding and losing details and emotional attachments as it goes. The largely practical effects (or so I understand) contribute excellently to this surreal quality, with literally disappearing details, spatial and temporal distortions, and even a sense of things being just on the tip of the mind, yet unreachable. It was particularly enjoyable in my delirious fever&amp;nbsp;state.&lt;/p&gt;
&lt;h2&gt;The&amp;nbsp;Wizard&lt;/h2&gt;
&lt;p&gt;&lt;span class="dquo"&gt;&amp;#8220;&lt;/span&gt;I love Powerglove. It&amp;#8217;s so&amp;nbsp;bad.&amp;#8221;&lt;/p&gt;
&lt;p&gt;This glorified ad for the &lt;span class="caps"&gt;NES&lt;/span&gt; is still a good time. I was obsessed with the Californian dinosaur statues after watching this movie when I was a&amp;nbsp;kid.&lt;/p&gt;
&lt;p&gt;One notably frustrating aspect of watching this movie now is how terrible all three finalists are at &lt;span class="caps"&gt;SMB3&lt;/span&gt;. Right at the beginning of the second level there&amp;#8217;s a koopa coming towards them, and they all stand still waiting for it. One of them even has the tanooki suit, but waits patiently for it to move into range to jump on its head instead of just spin attacking it. They all also missed the power-up box on the ground in the same level that they could have used the shell to get. Just awful to&amp;nbsp;watch.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Screenshot from The Wizard where the main character is waiting to jump on a koopa in SMB3 instead of just spin attacking them, and also a Jackie Chan WTF meme" src="https://blog.hyperlinkyourheart.com/images/covid-movies/exasperated_wizard.jpg" title="What are you doooing??"&gt;&lt;/p&gt;
&lt;h2&gt;Flight of the&amp;nbsp;Navigator&lt;/h2&gt;
&lt;p&gt;I used to love this one when I was a kid, but I barely remembered it - I didn&amp;#8217;t even remember the &amp;#8220;time-travel&amp;#8221; aspect, or as one character exclaimed: &amp;#8220;light-speed theory!&amp;#8221; Yeah ok&amp;nbsp;champ.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Screenshot of the meal robot, R.A.L.F." src="https://blog.hyperlinkyourheart.com/images/covid-movies/ralf.jpg" title="The real hero of this movie was R.A.L.F."&gt;&lt;/p&gt;
&lt;p&gt;It&amp;#8217;s quite a poignant story until the titular navigator boards the ship, at which point everything is explained by Max (the ship&amp;#8217;s computer) and the plot is basically over. The final third of the movie just consists of flying around, meeting cute alien creatures, zany antics and an increasingly annoying Max. It&amp;#8217;s fun and looks great, but I&amp;#8217;m not sure I would say it holds up for adult&amp;nbsp;viewing.&lt;/p&gt;
&lt;p&gt;One thing that definitely doesn&amp;#8217;t hold up is Sarah Jessica Parker&amp;#8217;s adult character flirting with a 12 year old. Women are only supposed to flirt with robots, aliens and weird anthropomorphic&amp;nbsp;ducks!&lt;/p&gt;
&lt;p&gt;&lt;img alt="Screenshot of Sarah Jessica Parker's character stroking the main character's face" src="https://blog.hyperlinkyourheart.com/images/covid-movies/inappropriate.jpg" title="This is an inappropriate flirtation Ms. In The City"&gt;&lt;/p&gt;
&lt;h2&gt;Wargames&lt;/h2&gt;
&lt;p&gt;Still amazing. Probably my favourite aspect of this movie is its realistic no-nonsense portrayal of hacking. There&amp;#8217;s no dumb made-up jargon or macguffins, no self-conscious cyberpunk mythologising, just a kid with a modem studying a target to guess a password, with his only motive to learn and have fun. It&amp;#8217;s so&amp;#8230; pure, so&amp;nbsp;unencumbered.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Screenshot of Matthew Broderick hacking" src="https://blog.hyperlinkyourheart.com/images/covid-movies/protovision.jpg" title="Just a boy and his IMSAI 8080."&gt;&lt;/p&gt;
&lt;p&gt;The performances reflect that as well, with Ally Sheedy and Matthew Broderick really capturing the goofy, carefree energy of youth at the beginning of the&amp;nbsp;movie.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Screenshot of Matthew Broderick smiling" src="https://blog.hyperlinkyourheart.com/images/covid-movies/murderface.jpg" title="A man with a face like this could get away with killing two women through reckless driving while holidaying in Northern Ireland in 1987."&gt;&lt;/p&gt;
&lt;p&gt;One thing that has always bugged me is that &lt;span class="caps"&gt;WOPR&lt;/span&gt;/Joshua isn&amp;#8217;t actually playing the game he&amp;#8217;s supposed to be - if he were, he would only make moves as America. Instead he fakes a series of Soviet attacks, which should be David&amp;#8217;s prerogative, seemingly to goad a real-life&amp;nbsp;response.&lt;/p&gt;
&lt;p&gt;Thankfully, whatever his motive, he ultimately fails, and instead learns a lesson about the futility of nuclear war in an incredibly powerful&amp;nbsp;climax.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Two screenshots of the big WOPR screen showing Ireland getting nuked" src="https://blog.hyperlinkyourheart.com/images/covid-movies/ireland_nuked.jpg" title="Was it really necessary to nuke Ireland twice?"&gt;&lt;/p&gt;
&lt;h2&gt;Star Trek:&amp;nbsp;Generations&lt;/h2&gt;
&lt;p&gt;I understand some people hate this movie, but I love it. It&amp;#8217;s the only movie that really captures &lt;span class="caps"&gt;TNG&lt;/span&gt;-era Trek at all, and it was incredibly exciting to see &amp;#8220;my&amp;#8221; trek on the big&amp;nbsp;screen.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Screenshot of Data saying that he hates the drink that Guinan has had him try" src="https://blog.hyperlinkyourheart.com/images/covid-movies/hatethis.jpg" title="Star Trek fans getting a taste of their first Next Generation movie."&gt;&lt;/p&gt;
&lt;p&gt;Probably my favourite aspect of this movie is Data&amp;#8217;s subplot of acquiring emotions and coming to terms with them. Sure, a lot of it is just comic relief (good comic relief though), but it also feels like a culmination of the character&amp;#8217;s arc from the&amp;nbsp;series.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Two shots of the Enterprise D saucer section crashing" src="https://blog.hyperlinkyourheart.com/images/covid-movies/crash.jpg" title="It's just like a long episode of the TV show!"&gt;&lt;/p&gt;
&lt;h2&gt;Star Trek: First&amp;nbsp;Contact&lt;/h2&gt;
&lt;p&gt;There are aspects of this movie that I like - I like the Borg, I like Picard&amp;#8217;s relationship to the Borg, I like seeing the pivotal moment of first contact, and Zephram Cochrane&amp;#8217;s warp ship looks&amp;nbsp;awesome.&lt;/p&gt;
&lt;p&gt;However&amp;#8230; there is much more about it that I don&amp;#8217;t&amp;nbsp;like.&lt;/p&gt;
&lt;p&gt;I think this was the introduction of the Borg queen, and while Alice Krige is amazing in the role, I think the existence of a queen changes the nature of the Borg somewhat - from something truly alien to something much more prosaic and uninteresting - and I don&amp;#8217;t appreciate&amp;nbsp;that.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Screenshot of Geordi" src="https://blog.hyperlinkyourheart.com/images/covid-movies/geordie.jpg" title="Also the vibes are way off on visor-less Geordi."&gt;&lt;/p&gt;
&lt;p&gt;Much worse than that are the changes to the character of Picard, who went from being a diplomat and a scholar to an action&amp;nbsp;hero.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Screenshot of Picard looking incredibly muscular" src="https://blog.hyperlinkyourheart.com/images/covid-movies/swoleactionpicard.jpg" title="The diplomacy of big biceps."&gt;&lt;/p&gt;
&lt;p&gt;Also the Borg time-travel plan as a last resort doesn&amp;#8217;t make any sense - if they have the means and will to assimilate Earth in the past they could do the time travelling from anywhere, there&amp;#8217;s no reason to barrel into the Sol system and do it only if their cube is destroyed. And they could just try again. But then, it&amp;#8217;s a common problem in Star Trek that things that are really easy to do one moment are impossible, or forgotten about, or work completely differently in the&amp;nbsp;next.&lt;/p&gt;
&lt;h2&gt;Star Trek &lt;span class="caps"&gt;IV&lt;/span&gt;: The Voyage&amp;nbsp;Home&lt;/h2&gt;
&lt;p&gt;This movie mostly consists of the Enterprise crew bumbling around San Francisco in the 80&amp;#8217;s and trying (and often failing) to grasp the cultural differences and blend in. It is ridiculous and hilarious, and I love&amp;nbsp;it!&lt;/p&gt;
&lt;p&gt;&lt;img alt="Shot of the Enterprise crew on a street in San Francisco in the 1980s" src="https://blog.hyperlinkyourheart.com/images/covid-movies/crew.jpg" title="Look at these adorable goofs."&gt;&lt;/p&gt;
&lt;p&gt;One thing this movie does really well is make the characters feel like they are from a very different time than the one they are visiting. Other Star Trek time-travel stories, such as the Voyager crew&amp;#8217;s visit to 1990&amp;#8217;s &lt;span class="caps"&gt;LA&lt;/span&gt;, Sisko and Bashir&amp;#8217;s visit to San Francisco in 2024, or the Enterprise D crew&amp;#8217;s visit to 19th century San Francisco (boy, they sure visit San Francisco a lot), don&amp;#8217;t quite capture the gulf of time in the same way - they generally come across as a little too&amp;nbsp;savvy.&lt;/p&gt;
&lt;p&gt;Another great aspect is how little fucks they give about telling people they are from the future, or even sharing technology with them. I bet the boys from the Department of Temporal Investigations had a heck of a time sorting this mess&amp;nbsp;out!&lt;/p&gt;
&lt;p&gt;&lt;img alt="Screenshot of Scotty entering the recipe for transparent aluminium into a Mac Plus" src="https://blog.hyperlinkyourheart.com/images/covid-movies/computer2.jpg" title="Apparently they had telepathic keyboards in the 80s."&gt;&lt;/p&gt;
&lt;p&gt;It&amp;#8217;s also brilliant that there&amp;#8217;s no real villain except for our society&amp;#8217;s indifference to environmental&amp;nbsp;degradation.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Shot of the Klingon ship hovering over a whaling ship" src="https://blog.hyperlinkyourheart.com/images/covid-movies/birdofprey.jpg" title="Or maybe the villain is just... whalers?"&gt;&lt;/p&gt;
&lt;h2&gt;Star Trek &lt;span class="caps"&gt;VII&lt;/span&gt;: The Undiscovered&amp;nbsp;Country&lt;/h2&gt;
&lt;p&gt;I think this was the first Star Trek movie I got to see in the cinema. I was definitely too young to understand the political allusions at the time, but I seem to remember enjoying it anyway. The themes are classic Star Trek stuff - overcoming prejudices, working to secure peace,&amp;nbsp;etc.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Screenshot of the Klingon character Chang, who has an eye patch" src="https://blog.hyperlinkyourheart.com/images/covid-movies/eyepatch.jpg" title="A somewhat less abstract enemy than the previous Star Trek movie on this list..."&gt;&lt;/p&gt;
&lt;p&gt;One striking aspect of this movie is how differently the Klingons are portrayed than they are in The Next Generation, particularly the early seasons. In this movie they are erudite, sophisticated, and almost Romulan in their sneakiness. In &lt;span class="caps"&gt;TNG&lt;/span&gt;&amp;#8217;s early seasons I guess we mostly just see Worf, but he is almost bestial, without the temperance and nuance of character that he would develop later. It&amp;#8217;s interesting that the releases overlap with this apparent&amp;nbsp;disconnect.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Screenshot of the Enterprise crew and the Klingon crew they are escorting having dinner together" src="https://blog.hyperlinkyourheart.com/images/covid-movies/dinner.jpg" title="Contrast this with scenes of Klingons dining in TNG..."&gt;&lt;/p&gt;
&lt;h2&gt;Back to the&amp;nbsp;Futures&lt;/h2&gt;
&lt;p&gt;I used to watch these once a year, but it has been a while since I&amp;#8217;ve watched any of them, and even longer since I&amp;#8217;ve watched all three in a&amp;nbsp;row.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Screenshot of Marty sidling away from the Doc as the Delorean is about to speed towards them, and the Doc looking at him accusingly" src="https://blog.hyperlinkyourheart.com/images/covid-movies/fear.jpg" title="How dare you?"&gt;&lt;/p&gt;
&lt;p&gt;I had some really weird misconceptions about these movies when I was a kid. In particular, I thought that every era had versions of the same characters - not family members who happen to look remarkably the same, but actually incarnations of the same characters. For example I thought 1885&amp;#8217;s Doc was a different Doc than 1985&amp;#8217;s Doc - and that Seamus was actually &amp;#8220;another Marty&amp;#8221;. It&amp;#8217;s hard to even grasp now what I was thinking with my undeveloped child brain, but somehow I also remember it really&amp;nbsp;clearly.&lt;/p&gt;
&lt;p&gt;Of course, now that I understand it fully, half the fun of a movie like this is poking holes in the time-travel logic. Obviously I&amp;#8217;m not saying anything new here, but a couple of the things that stood out to me on this viewing&amp;nbsp;were:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The way &amp;#8220;artifacts&amp;#8221; from other timelines change is ridiculous. If the future has been changed, why would they ever be in an intermediate state where things are fading&amp;nbsp;away?&lt;/li&gt;
&lt;li&gt;Marty has a picture of himself and his siblings, and after he changes the future they all fade away because they were never born - but why would the picture even have been taken&amp;nbsp;then?&lt;/li&gt;
&lt;li&gt;Similarly, Jennifer takes a fax from the future indicating that future Marty will be fired. After the timeline is changed, the contents of the paper are erased. Why would she have even taken the paper in this new timeline? It&amp;#8217;s a blank piece of paper, &lt;em&gt;what are you doing&amp;nbsp;Jennifer??&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;Also &lt;span class="caps"&gt;LOL&lt;/span&gt; when she holds it up to the Doc at the end of &lt;span class="caps"&gt;III&lt;/span&gt; and says &amp;#8220;It erased - what does it mean?&amp;#8221; He has no context for what this piece of paper&amp;nbsp;is!&lt;/li&gt;
&lt;li&gt;The same thing happens with the picture of the headstone in the third movie. It changes between showing different names, and then in the end it becomes a photo of an empty patch of ground. Why would they have taken a photo of an empty patch of&amp;nbsp;ground?&lt;/li&gt;
&lt;li&gt;Also hilarious is when newspapers are shown changing from one story to another, very conveniently about the same&amp;nbsp;person!&lt;/li&gt;
&lt;li&gt;There&amp;#8217;s a big question of why changes effect objects, but not people. Why does Marty not remember being raised by his new, cool parents, or that he has his dream truck? Why does he not remember his dad dying and his mom marrying Biff when that becomes the new reality? The only effect we see on Marty is when he is about to fade out of existence when it seems like his parents aren&amp;#8217;t going to get together - but he should also become a different person, like his siblings&amp;nbsp;did.&lt;/li&gt;
&lt;li&gt;The oft criticised scenes where Marty appears to inspire black men to achieve and create are really bizarre. His other actions change the future, but Mayor Wilson was already Mayor before the time-travel antics, and Chuck Berry had already made his music. They can&amp;#8217;t be time loops if Marty never changes along with everything&amp;nbsp;else. &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The other half of the fun is, as it has always been, Christopher Lloyd&amp;#8217;s wonderful&amp;nbsp;face:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Various shots of Doc Brown expressing emotion" src="https://blog.hyperlinkyourheart.com/images/covid-movies/docfaces.jpg" title="Such range."&gt;&lt;/p&gt;
&lt;p&gt;Despite having seen these movies so many times, this was the first time I realised that Marty&amp;#8217;s future daughter in &lt;span class="caps"&gt;BTTF2&lt;/span&gt; was also played by Michael J. Fox! And I still didn&amp;#8217;t even catch it when she was on screen, I only noticed it in the&amp;nbsp;credits!&lt;/p&gt;
&lt;p&gt;&lt;img alt="Shot of Marty's future daughter coming down the stairs" src="https://blog.hyperlinkyourheart.com/images/covid-movies/daughter.jpg" title="I still don't see it to be honest."&gt;&lt;/p&gt;
&lt;h2&gt;The&amp;nbsp;Warriors&lt;/h2&gt;
&lt;p&gt;I&amp;#8217;m not sure what age I was when I saw this before - probably too young - and all I really remembered about it was that there was a bunch of outrageously costumed gangs running around, and the bit at the end when they make it back to Coney&amp;nbsp;island.&lt;/p&gt;
&lt;p&gt;I get the impression that the violence depicted was shocking and realistic for the time, but now it seems quite tame - just some light, bloodless&amp;nbsp;brawling.&lt;/p&gt;
&lt;p&gt;One thing I can say after watching this now is that we don&amp;#8217;t have enough mime gangs or Star Trek alien baseball gangs around these days. I think crime would be a lot more fun if people injected some theatrics into&amp;nbsp;it.&lt;/p&gt;
&lt;p&gt;&lt;img alt="A couple of shots of the baseball gang who look like the Star Trek aliens with the half black and half white faces." src="https://blog.hyperlinkyourheart.com/images/covid-movies/startrekaliens.jpg" title="They could be straight out of DS9."&gt;
&lt;img alt="Shot of the mime gang coming out of the subway" src="https://blog.hyperlinkyourheart.com/images/covid-movies/mimes.jpg" title="Uh-oh I hope they don't mime beating me up!"&gt;&lt;/p&gt;
&lt;p&gt;The copy I watched this time was the director&amp;#8217;s cut, so I got comic-book style transitions and a voice-over opening which apparently weren&amp;#8217;t in the initial theatrical release. The transitions in particular suited it well because the gang uniforms are so&amp;nbsp;cartoonish.&lt;/p&gt;
&lt;h2&gt;Short&amp;nbsp;Circuit&lt;/h2&gt;
&lt;p&gt;The impact this movie had on me as a kid was to make me want to be friends with robots - and to be Ally Sheedy&amp;#8217;s&amp;nbsp;boyfriend.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Screenshot of Johnny 5 and Stephanie dancing" src="https://blog.hyperlinkyourheart.com/images/covid-movies/dance.jpg" title="I'm jealous of both of them."&gt;&lt;/p&gt;
&lt;p&gt;As with the character Max in Flight of the Navigator, Johnny 5 becomes increasingly annoying as the movie goes on, though he remains somewhat more endearing. His repeated insistence that he is alive and that he wants to remain so, and doesn&amp;#8217;t want to be involved in hurting any other creatures, was a powerful message to receive as a&amp;nbsp;kid.&lt;/p&gt;
&lt;p&gt;Unlike Flight of the Navigator the plot continues all the way through to a fantastic fake-out downer-upper&amp;nbsp;ending.&lt;/p&gt;
&lt;h2&gt;The&amp;nbsp;Rocketeer&lt;/h2&gt;
&lt;p&gt;&lt;img alt="Shot of the rocketeer flying towards a biplane" src="https://blog.hyperlinkyourheart.com/images/covid-movies/rocketeer.jpg" title="Outta my way, I'm a rocketman!"&gt;&lt;/p&gt;
&lt;p&gt;A barely controlled rocket-powered superhero foils the plans of some horrible Nazis - what more could you want from a movie? How about the gorgeous Jennifer Connolly also kicking&amp;nbsp;ass?&lt;/p&gt;
&lt;p&gt;&lt;img alt="Shot of Jennifer Connolly's character smashing something over a bad guy's head" src="https://blog.hyperlinkyourheart.com/images/covid-movies/jensmash.jpg" title="Smashing!"&gt;&lt;/p&gt;
&lt;p&gt;The action set-pieces in this movie lack the impact of those from more recent superhero movies, but it&amp;#8217;s also somewhat refreshing that he&amp;#8217;s just an ordinary guy who can take an ordinary amount of abuse. Also the art-deco Iron Man aesthetic is&amp;nbsp;amazing. &lt;/p&gt;
&lt;p&gt;Except&amp;#8230; how does he not burn his arse? Forget the helmet, what he needs are some thermally insulated&amp;nbsp;pants!&lt;/p&gt;
&lt;p&gt;&lt;img alt="Screenshot of the Hollywoodland sign becoming the Hollywood sign when a Nazi drops on the &amp;quot;land&amp;quot; part and explodes" src="https://blog.hyperlinkyourheart.com/images/covid-movies/hollywoodland.jpg" title="Did you know that the sign used to say Hollywoodland until they blew it up for this movie?"&gt;&lt;/p&gt;
&lt;p&gt;&lt;a name="note"&gt;Note:&lt;/a&gt; Once I was feeling better I got into other things and never finished off this post. It has actually been over six months now since I originally intended to post it, and therefore the opening paragraph is a bunch of &lt;em&gt;lies&lt;/em&gt;. I&amp;#8217;m finishing it off now because I think I have &lt;span class="caps"&gt;COVID&lt;/span&gt; again for the second (or maybe third) time, though less severe! &lt;a href="https://blog.hyperlinkyourheart.com/covid-movies.html#top" title="Back to top"&gt;Back to&amp;nbsp;top.&lt;/a&gt;&lt;/p&gt;</content><category term="Movies"></category><category term="fiction"></category><category term="coronavirus"></category><category term="star-trek"></category></entry><entry><title>From Hell’s Heart</title><link href="https://blog.hyperlinkyourheart.com/from-hells-heart.html" rel="alternate"></link><published>2024-05-14T22:46:00+02:00</published><updated>2024-05-14T22:46:00+02:00</updated><author><name>Kevin Houlihan</name></author><id>tag:blog.hyperlinkyourheart.com,2024-05-14:/from-hells-heart.html</id><summary type="html">&lt;p&gt;A post-mortem of my Ludum Dare 55 entry, From Hell&amp;#8217;s Heart, a twin-stick&amp;nbsp;shooter.&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;img alt="Demon character from my game bound in a circle of protection" src="https://blog.hyperlinkyourheart.com/images/from-hells-heart/my_boy_scaled.png" title="What have they done to my boy??"&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://hyperlinkyourheart.itch.io/from-hells-heart" title="From Hell's Heart Itch Page"&gt;From Hell&amp;#8217;s Heart&lt;/a&gt; is my entry for Ludum Dare 55, the first Ludum Dare I have participated in since I made &lt;a href="https://hyperlinkyourheart.itch.io/out-of-gas" title="Out of Gas Itch Page"&gt;Out of Gas&lt;/a&gt; for Ludum Dare 48 in&amp;nbsp;2021.&lt;/p&gt;
&lt;p&gt;I was not thrilled with it overall (and neither was anybody else - but we&amp;#8217;ll get to that), but it was good to get into working on actual gameplay programming again after several years of only working on tooling, and it has inspired me to do a lot more gamedev work&amp;nbsp;since.&lt;/p&gt;
&lt;h2&gt;The&amp;nbsp;Concept&lt;/h2&gt;
&lt;p&gt;I was quite disappointed by the theme. I had an idea for the theme &amp;#8220;It Spreads&amp;#8221;, which was one of the final round themes, that I was very invested in. I was going to make a zombie shooter where the weapons were all spreadables like jam and nutella and so on, and I had a lot of fun art and gameplay ideas for&amp;nbsp;that.&lt;/p&gt;
&lt;p&gt;So when the theme of &amp;#8220;Summoning&amp;#8221; was announced I was disappointed. But, the theme is rarely the one I would prefer, so I got to brainstorming and came up with the idea of &amp;#8220;reverse-Doom&amp;#8221;, which evolved into something not explicitly Doom related, but where you play as a demon fighting soldiers nonetheless. For a brief moment I was even considering calling it &amp;#8220;What if Doom but you&amp;#8217;re the&amp;nbsp;Demon&amp;#8221;.&lt;/p&gt;
&lt;p&gt;The thing I liked about this concept was that it allowed me to address the theme in two ways - you are a demon who has been summoned, and you can summon other demons to help you. However, the fact that it is perfectly playable and beatable without ever summoning your demon friends diminishes this&amp;nbsp;somewhat!&lt;/p&gt;
&lt;p&gt;One of my hopes for this jam was to try out my dialogue graph editor, &lt;a href="https://github.com/khoulihan/digression" title="Digression GitHub Page"&gt;Digression&lt;/a&gt;, in another game, and unfortunately there was no place for it with this concept. I used it heavily in &lt;a href="https://hyperlinkyourheart.itch.io/out-of-gas" title="Out of Gas Itch Page"&gt;Out of Gas&lt;/a&gt; and &lt;a href="https://hyperlinkyourheart.itch.io/gophers" title="Gophers Itch Page"&gt;Gophers&lt;/a&gt; in the past, but it has been fleshed out significantly since then and is ready to be put through its paces. Oh&amp;nbsp;well.&lt;/p&gt;
&lt;h2&gt;Art&lt;/h2&gt;
&lt;p&gt;I couldn&amp;#8217;t get Pyxel Edit to run on my new computer when setting up for the jam (it is Windows only, and I have run it using Wine in the past), so I decided to branch out and try &lt;a href="https://github.com/Orama-Interactive/Pixelorama" title="Pixelorama GitHub Page"&gt;Pixelorama&lt;/a&gt; - a pixel art editor built in Godot. Although my muscle memory from Pyxel edit tripped me up constantly in minor ways it was a good experience overall and I think I will continue using it going forward, as I always prefer open-source and native applications when they are available. The only thing I really found lacking was the way it handles the grid/tiles. There are no tools for quickly copying and manipulating tiles, and the grid is specified in the global settings instead of being per-file, even though one grid is unlikely to suit different files even in the same&amp;nbsp;project.&lt;/p&gt;
&lt;p&gt;I also had to adapt the way I managed spritesheets, as Pyxel edit allows you to define multiple animations in a single file, with the frames being tiles, while Pixelorama will only animate the entire file as a single unit. As such, character sprites are spread across multiple files each. In Godot this means swapping the sprite texture for each animation, but that didn&amp;#8217;t seem to cause any&amp;nbsp;problems.&lt;/p&gt;
&lt;p&gt;I made some big mistakes with the level art early on, initially designing it at twice the appropriate resolution for the characters I had in mind. I was able to take the basic design ideas and downscale them into the final wall and floor tiles easily enough, but it still wasted a lot of time. I designed them for an auto-tiling approach which I developed a while ago, where a tool script populates the floor tiles when you update the&amp;nbsp;walls.&lt;/p&gt;
&lt;p&gt;&lt;img alt="The initial oversized art I designed. The walls are about 5 times the height of the characters." src="https://blog.hyperlinkyourheart.com/images/from-hells-heart/humongowalls.png" title="Humongous walls, and some variants of the player character."&gt;&lt;/p&gt;
&lt;p&gt;I based the character shapes roughly on the design I came up with for &lt;a href="https://hyperlinkyourheart.itch.io/guerrilla-gardening" title="Guerrilla Gardening Itch Page"&gt;Guerrilla Gardening&lt;/a&gt; for Ludum Dare 41, which is very round and fun, and I&amp;#8217;ve found works really well for shooter games. It also features outlines for the characters which means there&amp;#8217;s less risk of ending up with characters that don&amp;#8217;t show up well against the&amp;nbsp;background!&lt;/p&gt;
&lt;p&gt;I was quite pleased with the art overall, though when I designed the wall and floor variants for the opening &amp;#8220;cutscene&amp;#8221; I realised that the game is far too red overall, as the characters looked much better against the bluer&amp;nbsp;background!&lt;/p&gt;
&lt;p&gt;&lt;img alt="Screenshot of part of the opening scene, with blue-grey crates against blue-grey walls and floor" src="https://blog.hyperlinkyourheart.com/images/from-hells-heart/crates.png" title="The crates had much less contrast though."&gt;&lt;/p&gt;
&lt;p&gt;I am determined next time I do a jam solo to not do full animations for each character or game element, and instead use the animation features of Godot to bounce, squash and spin things to bring them to life. This will give me much more time to create a variety of characters and objects, which I think would contribute more to a jam game than full run-cycle animations. The only place I used this kind of technique in this game was for the spinning and pulsing of the portal, and I think it was quite effective. People do some great things with this kind of animation and I am missing&amp;nbsp;out.&lt;/p&gt;
&lt;h2&gt;Music&lt;/h2&gt;
&lt;p&gt;Music is an element of jam games that I am always trying to find a new and better approach to, as it is something that I enjoy a lot but am not particularly skilled at. In the past I have used &lt;a href="https://www.beepbox.co" title="Beepbox"&gt;beepbox&lt;/a&gt;, &lt;a href="https://lmms.io/" title="LMMS Website"&gt;&lt;span class="caps"&gt;LMMS&lt;/span&gt;&lt;/a&gt; and &lt;a href="https://boscaceoil.net/" title="Bosca Ceoil Website"&gt;Bosca Ceoil&lt;/a&gt; with different degrees of&amp;nbsp;success.&lt;/p&gt;
&lt;p&gt;In the week prior to the jam I came across an application called &lt;a href="https://helio.fm/" title="Helio Website"&gt;Helio&lt;/a&gt; which I thought looked incredibly promising. It looked like it might be as easy to use as Bosca Ceoil but with a wider variety of possible instruments, so I was excited to try it&amp;nbsp;out.&lt;/p&gt;
&lt;p&gt;Another type of tool that I am always looking to bring into the fold is modular synth emulators. I have tried &lt;a href="https://vcvrack.com/" title="VCV Rack Website"&gt;&lt;span class="caps"&gt;VCV&lt;/span&gt; rack&lt;/a&gt; in the past but never managed to use it for a game, and I recently came across a fork of it called &lt;a href="https://cardinal.kx.studio/" title="Cardinal Website"&gt;Cardinal&lt;/a&gt; and had been practicing with that. I discovered that I could use Cardinal as a plugin for Helio and create instruments in it to control using Helio, so I planned to either do that or create a patch to generate all the music for the game, depending on what idea I was working on. The concept for From Hell&amp;#8217;s Heart didn&amp;#8217;t really seem to call for techno or ambient music, so I ended up trying the&amp;nbsp;latter.&lt;/p&gt;
&lt;p&gt;Unfortunately, things did not go smoothly at all with creating the music. I found Helio to be lacking in some essential features, and with a confusing menu system. It crashed constantly, forgetting instrument settings each time and sometimes changing the volume of notes or moving them around arbitrarily. I think the interface with Cardinal may have been responsible for some of this, and ultimately abandoned trying to use it for any instruments in favor of a selection of sound fonts. There was no way to preview the sound fonts in Helio, and I ended up previewing them on the command line using &lt;code&gt;fluidsynth&lt;/code&gt;, but not all of the ones I found that I liked worked when loaded into instruments in Helio. It was just a frustrating mess all&amp;nbsp;around.&lt;/p&gt;
&lt;p&gt;Most bafflingly in terms of missing features in Helio, I couldn&amp;#8217;t find a way of creating more than one pattern for an instrument, so I ended up having to use multiple tracks of the same instrument when I wanted a different&amp;nbsp;pattern.&lt;/p&gt;
&lt;p&gt;It also lacks a central mixer where effects can be applied. Instead you can add effects on a per-instrument basis. I tried to do this in Cardinal as well, but again abandoned that as time dragged on and the application kept&amp;nbsp;crashing.&lt;/p&gt;
&lt;p&gt;So overall it was a bit of a nightmare, took five precious hours, and the track ended up being overly repetitive, with just one melody repeated over and over with the only variation being different instruments coming in and out. I hated it when I was done, but it grew on me a bit when I put it in the game. It did kind of have the mood I was aiming&amp;nbsp;for.&lt;/p&gt;
&lt;p&gt;Every time I try to make music in Linux I feel like I am missing out on some brilliant workflow that ties a variety of different applications together with their different specialities, but I am just failing to grasp how it all works, and this time was no different. Back to &lt;span class="caps"&gt;LMMS&lt;/span&gt; next time I&amp;nbsp;think&amp;#8230;&lt;/p&gt;
&lt;h2&gt;Sound&amp;nbsp;Effects&lt;/h2&gt;
&lt;p&gt;Somewhere that I did get to use Cardinal was for the sound effects! I mostly used the Audible Instruments synth modules with a variety of different setting, and used &lt;a href="https://www.audacityteam.org/" title="Audacity Website"&gt;Audacity&lt;/a&gt; to record different notes being played for variety. Some of the results I was really pleased with (the demon snarls), others much less so (the enemy voices, which sounded like robots saying random words), but overall it worked pretty well. I would have preferred to do some foley stuff but there wasn&amp;#8217;t time, and it was a step up from &lt;a href="http://www.drpetter.se/project_sfxr.html" title="SFXR Website"&gt;&lt;span class="caps"&gt;SFXR&lt;/span&gt;&lt;/a&gt; at&amp;nbsp;least.&lt;/p&gt;
&lt;h2&gt;Code&lt;/h2&gt;
&lt;p&gt;Since my long-time project, &lt;a href="https://gamejolt.com/games/just-a-robot/185852" title="Just a Robot GameJolt Page"&gt;Just a Robot&lt;/a&gt;, is a shooter, I had a fair bit of base code to plunder for this jam. I don&amp;#8217;t think I did or learned anything particularly interesting this time around, but the base code did include a technique for showing a character silhouette when they are obscured by a wall which is interesting enough, and some automatic configuration of floor and mask tilemaps to allow rooms to be banged out quickly with little possibility of&amp;nbsp;error.&lt;/p&gt;
&lt;p&gt;The trick to the silhouettes is to create a mask of the parts of the walls that should obscure game entities using a &lt;code&gt;BackBufferCopy&lt;/code&gt; node, and then check that mask in a shader on any sprite that should be obscured. Objects in the game do not need to be children of the &lt;code&gt;TileMap&lt;/code&gt;, and in fact there is a different &lt;code&gt;TileMap&lt;/code&gt; for walls and&amp;nbsp;floors.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Screenshot of Godot editor scene tree showing mask TileMap and BackBufferCopy" src="https://blog.hyperlinkyourheart.com/images/from-hells-heart/backbuffer.png" title="Yes I used nodes as &amp;quot;folders&amp;quot; here, bad very bad."&gt;&lt;/p&gt;
&lt;p&gt;The mask &lt;code&gt;TileMap&lt;/code&gt; is populated automatically in the editor using a script like&amp;nbsp;this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="k"&gt;tool&lt;/span&gt;
&lt;span class="k"&gt;extends&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;TileMap&lt;/span&gt;

&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="k"&gt;export&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;copy_map&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb nb-Type"&gt;NodePath&lt;/span&gt;
&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="k"&gt;export&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;copy_from_layer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb nb-Type"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="k"&gt;export&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;copy_to_layer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb nb-Type"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="k"&gt;export&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;refresh_frequency&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb nb-Type"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;

&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="k"&gt;onready&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;_copy_map_node&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;get_node&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;copy_map&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;_process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;delta&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Engine&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;is_editor_hint&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;_tool_process&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;_tool_process&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;copy_map&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb nb-Type"&gt;null&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;and&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;refresh_frequency&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;_copy_map_node&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;get_node&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;copy_map&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;current_ticks&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_ticks_msec&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;or&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;current_ticks&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;refresh_frequency&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;current_ticks&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;_copy_map&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;_copy_map&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cells&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;_copy_map_node&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_used_cells&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;copy_from_layer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;clear_layer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;copy_to_layer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cell&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cells&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;not&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;_copy_map_node&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_cell_source_id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;copy_from_layer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cell&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set_cell&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="n"&gt;copy_to_layer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="n"&gt;cell&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="n"&gt;_copy_map_node&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_cell_source_id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;copy_from_layer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cell&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="n"&gt;_copy_map_node&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_cell_atlas_coords&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;copy_from_layer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cell&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="n"&gt;_copy_map_node&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_cell_alternative_tile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;copy_from_layer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cell&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fix_invalid_tiles&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The mask tileset just has 100% red everywhere that should be obscured, so the shader just checks for red in the screen texture and displays a grey colour instead of the sprite&amp;#8217;s texture anywhere it finds it, leaving the alpha&amp;nbsp;intact:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nx"&gt;shader_type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;canvas_item&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;uniform&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;hide_when_occluded&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;uniform&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;sampler2D&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;SCREEN_TEXTURE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;hint_screen_texture&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;filter_linear_mipmap&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fragment&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;vec4&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;mask&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;textureLod&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;SCREEN_TEXTURE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;SCREEN_UV&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m m-Double"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mask&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m m-Double"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mask&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m m-Double"&gt;0.9&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;hide_when_occluded&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nx"&gt;COLOR&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m m-Double"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nx"&gt;COLOR&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rgb&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;vec3&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m m-Double"&gt;0.2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m m-Double"&gt;0.2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m m-Double"&gt;0.2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;It turned out that people really hated the tight corridors and enemies being obscured though, so I probably won&amp;#8217;t be using this technique&amp;nbsp;again!&lt;/p&gt;
&lt;p&gt;A similar script to the one that populates the mask also populates a &lt;code&gt;TileMap&lt;/code&gt; with floor tiles based on the wall tiles. The floor tiles have a different size than the wall tiles however, so it is a bit longer and more involved. I think I would prefer to just put floor and wall tiles on different layers of the same &lt;code&gt;TileMap&lt;/code&gt; in the future, or as &lt;code&gt;TileMapLayers&lt;/code&gt; or whatever is being introduced in Godot&amp;nbsp;4.3&amp;#8230;&lt;/p&gt;
&lt;p&gt;Another neat thing I did in the editor was to have spawn points for portals and enemies draw a line to the thing they are associated with, or an obvious warning if they are not configured correctly. This helped me ensure that everything was set up properly when rushing through the room designs in the last few hours of the jam! I am trying to get into the habit of adding this sort of tooling to everything that might benefit from&amp;nbsp;it.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Screenshot of Godot editor showing lines from spawn points to associated entities" src="https://blog.hyperlinkyourheart.com/images/from-hells-heart/spawnlines.png" title="Lines, lines, everywhere are lines"&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="k"&gt;tool&lt;/span&gt;
&lt;span class="k"&gt;extends&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Marker2D&lt;/span&gt;


&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="k"&gt;export&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;destination&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;GameRoomSpawnPoint&lt;/span&gt;


&lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;_process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;delta&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Engine&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;is_editor_hint&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;_tool_process&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;_tool_process&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;queue_redraw&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;


&lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;_draw&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Engine&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;is_editor_hint&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;destination&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb nb-Type"&gt;null&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;_draw_to_destination&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;_draw_warning&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;


&lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;_draw_warning&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;draw_circle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb nb-Type"&gt;Vector2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;15.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb nb-Type"&gt;Color&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RED&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;_draw_to_destination&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;draw_circle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb nb-Type"&gt;Vector2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;15.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb nb-Type"&gt;Color&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GREEN&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;draw_line&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nb nb-Type"&gt;Vector2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;destination&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;global_position&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;position&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nb nb-Type"&gt;Color&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GREEN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;I also took from the base code a node-based state machine for enemy &lt;span class="caps"&gt;AI&lt;/span&gt;. This turned out to be a bit confusing and inflexible, and when I tried to introduce some new behaviour on the final day I ended up making the game crash constantly, and had to roll back. I&amp;#8217;m still unclear on exactly what went wrong there - I was getting null references due to the summoned allies despawning, even with null checks before referencing them. It was probably really obvious but I was exhausted by then. In any case there were other problems with the state machine I developed and I have started investigating the Godot plugin &lt;a href="https://github.com/bitbrain/beehave" title="Beehave GitHub Page"&gt;Beehave&lt;/a&gt; as an alternative for the&amp;nbsp;future.&lt;/p&gt;
&lt;p&gt;Something that did not work very well was the enemy navigation and avoidance. I completely misunderstood how the avoidance system was supposed to be used, so it was actually not in play at all. In experiments I&amp;#8217;ve done since the jam I got it working to some extent, but it seems like it does not work very well anyway, with agents just grinding to a halt in many circumstances even after avoiding an obstacle! The navigation itself would have worked much better with one small settings change of &lt;code&gt;path_postprocessing&lt;/code&gt; in the &lt;code&gt;NavigationAgent2D&lt;/code&gt; to &amp;#8220;edgecentered&amp;#8221;, as I discovered later. As I had it for the jam, navigation agents are always getting stuck on wall&amp;nbsp;corners&amp;#8230;&lt;/p&gt;
&lt;h2&gt;Results&lt;/h2&gt;
&lt;p&gt;My game got lots of nice commments, and also a lot of complaints about out-of-bounds glitches, enemies being obscured by the walls, and being too&amp;nbsp;easy.&lt;/p&gt;
&lt;p&gt;I was uninspired by the theme this time around and my skills were somewhat rusty in every area, and the game was generic and buggy as a result, so I was unsurprised by the negative comments. I did think it looked good and was reasonably juicy and sounded alright. I wasn&amp;#8217;t expecting great overall ratings, but I did think it would do ok in the graphics category at least. However, the ratings were quite bad across the board, at least compared to previous&amp;nbsp;jams!&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style="text-align: left;"&gt;Category&lt;/th&gt;
&lt;th style="text-align: right;"&gt;Rating&lt;/th&gt;
&lt;th style="text-align: right;"&gt;Placing&lt;/th&gt;
&lt;th style="text-align: right;"&gt;Percentile&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;Overall&lt;/td&gt;
&lt;td style="text-align: right;"&gt;3.571&lt;/td&gt;
&lt;td style="text-align: right;"&gt;574&lt;/td&gt;
&lt;td style="text-align: right;"&gt;65th&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;Fun&lt;/td&gt;
&lt;td style="text-align: right;"&gt;3.63&lt;/td&gt;
&lt;td style="text-align: right;"&gt;418&lt;/td&gt;
&lt;td style="text-align: right;"&gt;75th&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;Theme&lt;/td&gt;
&lt;td style="text-align: right;"&gt;3.413&lt;/td&gt;
&lt;td style="text-align: right;"&gt;792&lt;/td&gt;
&lt;td style="text-align: right;"&gt;52nd&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;Innovation&lt;/td&gt;
&lt;td style="text-align: right;"&gt;2.739&lt;/td&gt;
&lt;td style="text-align: right;"&gt;1080&lt;/td&gt;
&lt;td style="text-align: right;"&gt;35th&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;Humor&lt;/td&gt;
&lt;td style="text-align: right;"&gt;2.609&lt;/td&gt;
&lt;td style="text-align: right;"&gt;823&lt;/td&gt;
&lt;td style="text-align: right;"&gt;50th&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;Graphics&lt;/td&gt;
&lt;td style="text-align: right;"&gt;3.935&lt;/td&gt;
&lt;td style="text-align: right;"&gt;428&lt;/td&gt;
&lt;td style="text-align: right;"&gt;74th&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;Audio&lt;/td&gt;
&lt;td style="text-align: right;"&gt;3.619&lt;/td&gt;
&lt;td style="text-align: right;"&gt;343&lt;/td&gt;
&lt;td style="text-align: right;"&gt;79th&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;Mood&lt;/td&gt;
&lt;td style="text-align: right;"&gt;3.457&lt;/td&gt;
&lt;td style="text-align: right;"&gt;686&lt;/td&gt;
&lt;td style="text-align: right;"&gt;59th&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;This game did worse in almost every category in percentile terms than my Ludum Dare 38 entry which I didn&amp;#8217;t even complete - it was just an opening scene with a short dialog and no gameplay. Quite a drop from my &amp;#8220;Gophers&amp;#8221;&amp;nbsp;peak!&lt;/p&gt;
&lt;p&gt;&lt;img alt="Ratings Graph" src="https://blog.hyperlinkyourheart.com/images/from-hells-heart/ratingsgraph.png"&gt;
&lt;img alt="Percentile Graph" src="https://blog.hyperlinkyourheart.com/images/from-hells-heart/percentilegraph.png"&gt;&lt;/p&gt;
&lt;h2&gt;Post-jam and&amp;nbsp;Take-away&lt;/h2&gt;
&lt;p&gt;I won&amp;#8217;t make my usual promise to work on a post-jam version of this game, both because it&amp;#8217;s not interesting enough to be worth it, and because I never keep that promise&amp;nbsp;anyway!&lt;/p&gt;
&lt;p&gt;Instead I have been inspired to start working on &lt;a href="https://gamejolt.com/games/just-a-robot/185852" title="Just a Robot GameJolt Page"&gt;Just a Robot&lt;/a&gt; again since the jam, trying to get combat and enemy behaviour working the way I have always envisioned them, to see if it would actually be fun. The idea is to make it a cover-based shooter where the enemies abilities are close to the player&amp;#8217;s, more or less, and combat feels weighty and dangerous in a way that is distinct from bullet-hell&amp;nbsp;shooters.&lt;/p&gt;
&lt;p&gt;I successfully implemented a cover system for the player, but when I went to enable the enemies to use it and switch to using Beehave for their &lt;span class="caps"&gt;AI&lt;/span&gt; I quickly discovered that the way I had built them so far was too centralised and inheritance based, so after watching a few tutorials and considering things a bit I decided to start again mostly from scratch with a more composition-based approach. This is working really well for the player so far and I&amp;#8217;m nearly back at the same point as I was&amp;nbsp;previously.&lt;/p&gt;
&lt;p&gt;I also thought about and experimented with tiling approaches a bit, creating both a very small scale autotiling tileset with multiple terrains, and a mockup of a more organic looking tileset with a wide variety of designs, angled walls and floor sections and the like. I am tired of creating boxy, uninteresting levels. I was initially inspired in my gamedev journey by the art and design of Hyper Light Drifter, and I want to get back to achieving something of that look and sense of verticality - maybe not in jam games, but in Just a&amp;nbsp;Robot.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Complex tiles mockup" src="https://blog.hyperlinkyourheart.com/images/from-hells-heart/HLD-like Room Experiment.png" title="Too bright for this game, but moving in the right direction I think"&gt;&lt;/p&gt;
&lt;p&gt;I started on a greybox version of the above to actually use in designing the game, but it&amp;#8217;s not quite finished&amp;nbsp;yet.&lt;/p&gt;</content><category term="Game Development"></category><category term="godot"></category><category term="godot4"></category><category term="gdscript"></category><category term="ludum-dare"></category><category term="music"></category><category term="pixelart"></category></entry><entry><title>I Accidentally Visual Scripting</title><link href="https://blog.hyperlinkyourheart.com/accidental-visual-scripting.html" rel="alternate"></link><published>2023-11-30T22:31:00+01:00</published><updated>2023-11-30T22:31:00+01:00</updated><author><name>Kevin Houlihan</name></author><id>tag:blog.hyperlinkyourheart.com,2023-11-30:/accidental-visual-scripting.html</id><summary type="html">&lt;p&gt;I seem to be accidentally implementing a visual scripting system as part of my&amp;nbsp;tooling&amp;#8230;&lt;/p&gt;</summary><content type="html">&lt;p&gt;For some months now I&amp;#8217;ve been trying to make some progress on the game I&amp;#8217;ve been &amp;#8220;working on&amp;#8221; for probably the better part of a decade, &lt;a href="https://gamejolt.com/games/just-a-robot/185852" title="Just a Robot on GameJolt"&gt;Just a Robot&lt;/a&gt; (yep, 7 years ago was the first post there, and it was already in progress for a few years before that!). I haven&amp;#8217;t really been working on it for most of that time, though it has always been in the back of my&amp;nbsp;mind.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Variations on the character art over the years, including sprites and a portrait" src="https://blog.hyperlinkyourheart.com/images/accidental-visual-scripting/art_changes.png" title="All I actually seem to do is redesign the art..."&gt;&lt;/p&gt;
&lt;p&gt;While I have re-implemented some basic &lt;em&gt;gunplay&lt;/em&gt; mechanics, and experimented with auto-tiling and the like, my main task has been improving the editor for cutscene graphs that I designed previously (and which I&amp;#8217;ve &lt;a href="https://blog.hyperlinkyourheart.com/coroutine-callbacks.html" title="Coroutine Callbacks"&gt;mentioned&lt;/a&gt; a &lt;a href="https://blog.hyperlinkyourheart.com/all-my-yields.html" title="All My Yields post"&gt;few times before&lt;/a&gt;) based on my experience of using it in a &lt;a href="https://blog.hyperlinkyourheart.com/gophers-post-mortem.html" title="Gophers Post-mortem"&gt;couple&lt;/a&gt; of &lt;a href="https://blog.hyperlinkyourheart.com/out-of-gas-post-mortem.html" title="Out of Gas Post Mortem"&gt;jam games&lt;/a&gt;, and the anticipated requirements for this&amp;nbsp;game.&lt;/p&gt;
&lt;p&gt;I am also hoping that it will be something that other people might find useful, and that I can release in the Godot asset library. As such I try to do things in a generalised and user-friendly way, with nothing that would tie it specifically to my game, or rough edges that I would just ignore myself but be embarrassed if other people had to deal with&amp;nbsp;them.&lt;/p&gt;
&lt;p&gt;As a result, it&amp;#8217;s taking quite a&amp;nbsp;while!&lt;/p&gt;
&lt;p&gt;One thing I&amp;#8217;ve noticed is that as I try to maintain flexibility I seem to be implementing tiny haphazard visual scripting systems for calculating values and defining conditions. Of course the graph editing is itself a type of visual scripting, but that that was both what I was expecting to be designing, and has well established &lt;span class="caps"&gt;UI&lt;/span&gt; conventions in the engine. I don&amp;#8217;t think there are any conventions or controls for defining values and conditions in the way that I&amp;nbsp;am.&lt;/p&gt;
&lt;h2&gt;Variable&amp;nbsp;Changes&lt;/h2&gt;
&lt;p&gt;In the initial incarnation of the graph editor, it was possible to set the value of variables, and branch based on the value of variables, but the way it was implemented was&amp;#8230; not&amp;nbsp;good.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Variable names were entered as strings. This seemed like a problem to me - as the scope of a project grew I anticipated that it would become harder and harder to keep track of what variables were being used, and if they were entered correctly&amp;nbsp;everywhere.&lt;/li&gt;
&lt;li&gt;Values were also always strings - if you wanted a boolean, just enter &amp;#8220;true&amp;#8221; or&amp;nbsp;&amp;#8220;false&amp;#8221;!&lt;/li&gt;
&lt;li&gt;There was no scoping, just one global pool of variables for all&amp;nbsp;graphs.&lt;/li&gt;
&lt;li&gt;You could only assign constant values to variables. No incrementing or decrementing, arbitrary calculations, or using the values of other&amp;nbsp;variables.&lt;/li&gt;
&lt;li&gt;You could only compare to constant values for&amp;nbsp;branching.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img alt="Screenshot of early version of the editor, showing string variable name and value fields" src="https://blog.hyperlinkyourheart.com/images/accidental-visual-scripting/dialogue_editor_example.png" title="This Naomi be Wolf"&gt;&lt;/p&gt;
&lt;p&gt;My initial changes to improve this situation were to introduce scoping and type definitions for variables. Variables could be scoped to the graph, to the area (i.e. &amp;#8220;level&amp;#8221; or &amp;#8220;room&amp;#8221; or however the game wanted to define it), or be global. They could also be &lt;code&gt;boolean&lt;/code&gt;, &lt;code&gt;int&lt;/code&gt;, &lt;code&gt;float&lt;/code&gt;, or &lt;code&gt;string&lt;/code&gt;. The &lt;span class="caps"&gt;UI&lt;/span&gt; would change to reflect the type, so you would get a checkbox for booleans and a numbox for&amp;nbsp;numbers.&lt;/p&gt;
&lt;p&gt;This was better, but still kind of awful, because everywhere a variable was used the scope and type would have to be selected again! Unlike in code there was no way to declare a variable once and then use it elsewhere with its type and scope already&amp;nbsp;known.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Screenshot of one of the graphs from my game &amp;quot;Out of Gas&amp;quot;, showing scoped boolean variables with values set by checkboxes" src="https://blog.hyperlinkyourheart.com/images/accidental-visual-scripting/teenagers_dialogue.png" title="Teenagers up to no good as usual"&gt;&lt;/p&gt;
&lt;p&gt;So next&amp;#8230; I implemented variable declarations, more or less! These are defined in the project, and anywhere that a variable is required in a graph it can be selected from a searchable&amp;nbsp;dialog.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Dialog for defining a variable" src="https://blog.hyperlinkyourheart.com/images/accidental-visual-scripting/variable_definition.png" title="Defining a variable"&gt;
&lt;img alt="Variable set node with the variable selection control and a boolean value" src="https://blog.hyperlinkyourheart.com/images/accidental-visual-scripting/predefined_variable_set.png" title="Variable Set node"&gt;
&lt;img alt="Dialog for finding a variable" src="https://blog.hyperlinkyourheart.com/images/accidental-visual-scripting/variable_selection.png" title="Selecting a variable"&gt;&lt;/p&gt;
&lt;p&gt;Now I felt like I was getting places, though the problem of only being able to set and compare constants remains, and is what I&amp;#8217;m currently tackling. But more about that in a&amp;nbsp;minute.&lt;/p&gt;
&lt;h2&gt;Choice&amp;nbsp;Conditions&lt;/h2&gt;
&lt;p&gt;Another feature of the initial version of the graph editor was the ability to make dialogue choices only conditionally available to the player - for example making a choice dependent on having encountered a particular character or completed a particular quest. This was of course based on the comparison of a variable to a string constant, so it had all the same deficiencies as everything else about the variables, as well as a few of its&amp;nbsp;own:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Only one variable could be used for each&amp;nbsp;choice.&lt;/li&gt;
&lt;li&gt;The only comparison available was equality, no greater or less than or anything like&amp;nbsp;that.&lt;/li&gt;
&lt;li&gt;There was no possibility of&amp;nbsp;negation.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The minimal improvement would be to allow a comparison of a single variable with a selectable operator, and a constant value. But what if the choice depends on the value of multiple variables? What if you want to make different choices available if the player has encountered a character but not completed a quest, than if they have done both? This could maybe be achieved using branching nodes to set a third variable, but that seemed like jumping through a lot of unnecessary hoops. I wanted to be able to define complex conditions directly on the&amp;nbsp;choices.&lt;/p&gt;
&lt;p&gt;My solution was to add a dialog where an arbitrarily complex condition can be defined - though currently only with constants on the right side of any&amp;nbsp;operator.&lt;/p&gt;
&lt;p&gt;&lt;img alt="A screenshot of the condition definition dialog alongside the node that it was invoked from" src="https://blog.hyperlinkyourheart.com/images/accidental-visual-scripting/farnsworth.png" title="The Farnsworth Condition"&gt;&lt;/p&gt;
&lt;p&gt;As you can see above, the condition is structured as a tree with boolean operators grouping the results of comparison operators. The whole condition is summarised as its (frankly much more understandable) equivalent&amp;nbsp;GDScript.&lt;/p&gt;
&lt;p&gt;So mission mostly accomplished - it was now possible to define conditions on choices with an arbitrary number of clauses. However, it was starting to seem like a lot of &lt;span class="caps"&gt;UI&lt;/span&gt; complexity to achieve something that is quite simple to do in code - almost like a type of visual&amp;nbsp;scripting&amp;#8230;&lt;/p&gt;
&lt;h2&gt;Visualising&amp;nbsp;Values&lt;/h2&gt;
&lt;p&gt;Now it&amp;#8217;s time to tackle the other side of the equation - the values to set or compare the chosen variables&amp;nbsp;to.&lt;/p&gt;
&lt;p&gt;For setting variables, I wanted it to be possible to increment or decrement values as well as setting static ones&amp;#8230; But why not also allow values to be multiplied or divided? What if you want to set a variable to 2x another variable, or the result of a more complex calculation? These don&amp;#8217;t seem like particularly likely requirements for my game, but why reduce the flexibility by only allowing a handful of fixed&amp;nbsp;operations?&lt;/p&gt;
&lt;p&gt;For comparisons, I initially only considered allowing a choice between a constant or another variable. But would these not also benefit from more flexibility? What if you want to check if a variable is greater than half another variable? If setting variables was as flexible as described above, this could be achieved by setting a temporary variable, but that seems unnecessarily round-about and annoying to have to do. Since the task is the same for both situations (obtain a value for the right side of an operator), it seemed like it would be prudent to create one control that would cover&amp;nbsp;both.&lt;/p&gt;
&lt;p&gt;Another factor is that most of the above concerns only apply to integer and float variables. Booleans have another set of operators that might be applied to them. Strings have a more restricted set of operators, but there are a variety of functions that you might want to apply, or methods that you might want to call on them - &lt;code&gt;to_lower&lt;/code&gt;, &lt;code&gt;rstrip&lt;/code&gt;, &lt;code&gt;replacen&lt;/code&gt;, etc. In fact, the same might be true of integers and&amp;nbsp;floats&amp;#8230;&lt;/p&gt;
&lt;p&gt;With all that in mind the requirements have become quite complex - I&amp;#8217;m faced with implementing a small but significant subset of GDScript in a &lt;span class="caps"&gt;GUI&lt;/span&gt;!&lt;/p&gt;
&lt;p&gt;The design I have so far allows for multiple variables or constants to have operators to be applied between them, grouped by brackets if necessary, and for a selection of appropriate (for the type) functions to be&amp;nbsp;called:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Screenshot of the design of the proposed value calculation control in the designer" src="https://blog.hyperlinkyourheart.com/images/accidental-visual-scripting/value_calculation_mockup.png" title="That's a really long and weird way to write 1..."&gt;&lt;/p&gt;
&lt;p&gt;One thing that annoys me about this is that it&amp;#8217;s much the same structure as is involved in creating the conditions (a tree), but uses completely different controls and looks and works completely differently. It&amp;#8217;s going to look quite strange when they are side by side in the conditions dialog&amp;#8230; However, the tree control used for the conditions probably suits that better because the elements need to be selectable, and the &amp;#8220;prefix notation&amp;#8221; also suits it better I think, while it would be unfamiliar for most people for mathematical&amp;nbsp;operators.&lt;/p&gt;
&lt;p&gt;I can&amp;#8217;t really tell at this point if this is at all intuitive or if there&amp;#8217;s an obviously much simpler solution that I&amp;#8217;ve overlooked. But it certainly makes me long to be able to just enter the values as code, regardless of their&amp;nbsp;composition.&lt;/p&gt;
&lt;h2&gt;Could I Uuuh&amp;#8230; Do&amp;nbsp;That?&lt;/h2&gt;
&lt;p&gt;I have been thinking about what would be involved in allowing the user to enter conditions and values as the GDScript code they would likely already be familiar&amp;nbsp;with.&lt;/p&gt;
&lt;p&gt;Godot includes an &lt;code&gt;Expression&lt;/code&gt; class which can be used to parse and execute arbitrary expressions, so conceivably that could be used to run whatever the user input, and make the entire GDScript language available to them. It looks like it can even parse the text for errors before running it, which would be all that I would want to do in the editor anyway. One likely difficulty is that the referenced variables would not actually be GDScript variables at runtime, but entries in one of several dictionaries, so I might have to write my own parser anyway to pick those out and replace them. I&amp;#8217;m not sure I really want to do&amp;nbsp;that.&lt;/p&gt;
&lt;p&gt;If that didn&amp;#8217;t work out, the alternative would be to parse the input myself (even more parsing!) and convert it into the same resource structures that I&amp;#8217;m intending to have the &lt;span class="caps"&gt;UI&lt;/span&gt; create. This would likely be much more limited in which language constructs and operations it could support - and that might cause confusion or frustration for the&amp;nbsp;user.&lt;/p&gt;
&lt;p&gt;And, there are reasons why allowing these things to be defined as code might not be a good idea&amp;nbsp;anyway:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;I don&amp;#8217;t really like the idea of having to switch from a &lt;span class="caps"&gt;GUI&lt;/span&gt; way of doing things to a code way of doing things when the rest of the plugin is very much &lt;span class="caps"&gt;GUI&lt;/span&gt;&amp;nbsp;driven.&lt;/li&gt;
&lt;li&gt;I don&amp;#8217;t like the fact that the predefined variables would not be selectable, negating some of their utility. Or at least, to make them selectable I would have to implement some sort of auto-completion on top of everything else, which might be beyond&amp;nbsp;me! &lt;/li&gt;
&lt;li&gt;Some people use C# with Godot, and it&amp;#8217;s unclear if the &lt;code&gt;Expression&lt;/code&gt; class can parse C# - I suspect it&amp;nbsp;can&amp;#8217;t.&lt;/li&gt;
&lt;li&gt;Using the &lt;code&gt;Expression&lt;/code&gt; class would likely allow arbitrary GDScript to be entered and executed during graph processing. That might be too much flexibility! If I want to allow that it will be its own node&amp;nbsp;type.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;One other &lt;span class="caps"&gt;GUI&lt;/span&gt; option I can think of would be to allow value calculations and conditions to be defined using their own type of graphs. This might have the advantage of using well established &lt;span class="caps"&gt;UI&lt;/span&gt; conventions and existing controls. On the other hand, a graph editor is not ideal for defining a tree, and it would probably appear even more unnecessarily complex and confusing for the undoubtedly most common use cases of setting variables to constant values and defining simple conditions based on single variables. It would also not be any less work for&amp;nbsp;me!&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;&lt;span class="caps"&gt;UI&lt;/span&gt; is hard &lt;i class="fas fa-sad-cry body-icon" /&gt; &lt;/p&gt;</content><category term="Game Development"></category><category term="godot"></category><category term="godot4"></category><category term="gdscript"></category><category term="cutscenegrapheditor"></category></entry><entry><title>All My Yield()s, Gone!</title><link href="https://blog.hyperlinkyourheart.com/all-my-yields.html" rel="alternate"></link><published>2023-06-23T17:10:00+02:00</published><updated>2023-06-23T17:10:00+02:00</updated><author><name>Kevin Houlihan</name></author><id>tag:blog.hyperlinkyourheart.com,2023-06-23:/all-my-yields.html</id><summary type="html">&lt;p&gt;The removal of yield() in Godot 4 means a new approach is required for some of its use&amp;nbsp;cases.&lt;/p&gt;</summary><content type="html">&lt;p&gt;In a &lt;a href="https://blog.hyperlinkyourheart.com/coroutine-callbacks.html" title="Coroutine Callbacks post"&gt;previous post&lt;/a&gt; I described a way to use the state object returned by a &lt;code&gt;yield()&lt;/code&gt; call to control the traversal of a graph - specifically, a graph describing a cutscene or dialogue - where some nodes in the graph require waiting on input from the user or some other event before&amp;nbsp;proceeding.&lt;/p&gt;
&lt;p&gt;In &lt;a href="https://godotengine.org/" title="The game engine you waited for."&gt;Godot 4&lt;/a&gt; the &lt;code&gt;yield&lt;/code&gt; function was replaced with the &lt;code&gt;await&lt;/code&gt; keyword. This has the same basic purpose: to suspend execution of the current function and return to the caller, to be resumed at a later time. However, it does not return the state object that &lt;code&gt;yield&lt;/code&gt; did, so there is no built-in way to resume the function from the caller (that I can see,&amp;nbsp;anyway).&lt;/p&gt;
&lt;p&gt;Fortunately, it is not difficult to recreate the functionality. The first thing we need to do is define a very simple class that we can include instances of in the signals from the graph&amp;nbsp;controller:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;ProceedSignal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;signal&lt;/span&gt; &lt;span class="n"&gt;ready_to_proceed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;choice&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;func&lt;/span&gt; &lt;span class="n"&gt;proceed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;choice&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;ready_to_proceed&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;emit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;choice&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This class includes a signal and a method that the consumer of the graph can call to tell the graph controller that it can proceed to the next node - similar to the &lt;code&gt;resume()&lt;/code&gt; method on the coroutine state object in Godot&amp;nbsp;3.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;_await_response&lt;/code&gt; function which previously yielded to create a resumable coroutine state, now just returns a new instance of this class. It could alternatively just be created directly where this function is&amp;nbsp;called:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;func&lt;/span&gt; &lt;span class="n"&gt;_await_response&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ProceedSignal&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;In &lt;code&gt;process_cutscene()&lt;/code&gt; we now &lt;code&gt;await&lt;/code&gt; calls to process node types that require waiting on the consumer, rather than &lt;code&gt;yield&lt;/code&gt;ing&amp;nbsp;them:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;func&lt;/span&gt; &lt;span class="n"&gt;process_cutscene&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cutscene&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;_graph_stack&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="n"&gt;_local_store&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
    &lt;span class="n"&gt;_current_graph&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cutscene&lt;/span&gt;
    &lt;span class="n"&gt;_current_node&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_current_graph&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;root_node&lt;/span&gt;

    &lt;span class="o"&gt;...&lt;/span&gt;

    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;_current_node&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;null&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;_current_node&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="n"&gt;DialogueTextNode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_process_dialogue_node&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;_current_node&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="n"&gt;BranchNode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;_process_branch_node&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="o"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;And when processing such a node, we just create the &lt;code&gt;ProceedSignal&lt;/code&gt; object, emit the relevant signal with it, and then await the &lt;code&gt;ready_to_proceed&lt;/code&gt; signal from&amp;nbsp;it:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;func&lt;/span&gt; &lt;span class="n"&gt;_process_dialogue_node&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;

    &lt;span class="o"&gt;...&lt;/span&gt;

    &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_current_node&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;

    &lt;span class="n"&gt;var&lt;/span&gt; &lt;span class="n"&gt;character_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;null&lt;/span&gt;
    &lt;span class="n"&gt;var&lt;/span&gt; &lt;span class="n"&gt;variant_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;null&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;_current_node&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;character&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;null&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;character_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_current_node&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;character&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;character_name&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;_current_node&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;character_variant&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;null&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;variant_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_current_node&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;character_variant&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;variant_name&lt;/span&gt;

    &lt;span class="n"&gt;var&lt;/span&gt; &lt;span class="n"&gt;process&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_await_response&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;call_deferred&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;_emit_dialogue_signal&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;character_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;variant_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;process&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;process&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ready_to_proceed&lt;/span&gt;

    &lt;span class="n"&gt;_current_node&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_get_node_by_id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_current_node&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;next&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Nothing much changes from the consumer&amp;#8217;s point of view, it just needs to store the object and then call the &lt;code&gt;proceed()&lt;/code&gt; method when it&amp;#8217;s&amp;nbsp;ready:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;func&lt;/span&gt; &lt;span class="n"&gt;_on_cutscene_controller_dialogue_display_requested&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;character_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;character_variant&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;process&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# Hang on to the process object so we can tell the cutscene controller&lt;/span&gt;
    &lt;span class="c1"&gt;# to continue when we&amp;#39;re ready to proceed&lt;/span&gt;
    &lt;span class="n"&gt;_current_process&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;process&lt;/span&gt;

    &lt;span class="o"&gt;...&lt;/span&gt;


&lt;span class="n"&gt;func&lt;/span&gt; &lt;span class="n"&gt;_on_dialogue_display_continue_clicked&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;DialogueDisplay&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hide&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;_current_process&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;proceed&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;That&amp;#8217;s all the changes required for this project! Of course, the coroutine state object also had a property indicating if it was resumable or not, &lt;code&gt;is_valid&lt;/code&gt;. It would not be difficult at all to reproduce this by simply adding such a property (perhaps behind a setter that would make it read-only except internally), and setting it to false once &lt;code&gt;proceed()&lt;/code&gt; is&amp;nbsp;called.&lt;/p&gt;
&lt;p&gt;It could also be expanded to allow more complex communication between the coroutine and the consumer, or to make a long running coroutine cancellable. The controller can only &lt;code&gt;await&lt;/code&gt; one type of signal to continue, but you could have it pass different instructions when resuming e.g. you could give it &lt;code&gt;stop()&lt;/code&gt; and &lt;code&gt;proceed()&lt;/code&gt; methods. Additional properties on the signal object could be used to pass back other data without having to pass it in the signal at&amp;nbsp;all.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;enum&lt;/span&gt; &lt;span class="n"&gt;ProceedSignalType&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;STOP&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;PROCEED&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;ProceedSignal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;signal&lt;/span&gt; &lt;span class="n"&gt;ready_to_proceed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;signal_type&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;var&lt;/span&gt; &lt;span class="n"&gt;consumer_state&lt;/span&gt;

    &lt;span class="n"&gt;func&lt;/span&gt; &lt;span class="n"&gt;stop&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="n"&gt;ready_to_proceed&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;emit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ProceedSignalType&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;STOP&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;func&lt;/span&gt; &lt;span class="n"&gt;proceed&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="n"&gt;ready_to_proceed&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;emit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ProceedSignalType&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PROCEED&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Cutscene Graph Editor&amp;nbsp;Status&lt;/h2&gt;
&lt;p&gt;The cutscene graph editor has been upgraded to support Godot 4, at a &lt;a href="https://github.com/khoulihan/godot4-cutscene-graph-editor" title="Cutscene Graph Editor project on GitHub"&gt;new home&lt;/a&gt;. I&amp;#8217;ve also added a bunch of minor features, such as multi-node deletion, copy &lt;span class="amp"&gt;&amp;amp;&lt;/span&gt; paste and duplication support, and support for dragging from an output port to create a new&amp;nbsp;node.&lt;/p&gt;
&lt;p&gt;I&amp;#8217;m now working on improving some parts of the tool that were lacking in flexibility. My current task is improving the addition of conditions to the choice and random nodes, which previously only allowed a single variable to be compared for equality to determine if a branch should be considered. The new system moves the condition specification &lt;span class="caps"&gt;UI&lt;/span&gt; out of the nodes themselves and into a dialog box, and allows any number of variables to be evaluated using a variety of different&amp;nbsp;operators.&lt;/p&gt;
&lt;p&gt;&lt;img alt="The Farnsworth Condition" src="https://blog.hyperlinkyourheart.com/images/all-my-yields/graph.png" title="The Farnsworth Condition"&gt;&lt;/p&gt;
&lt;p&gt;Future plans include new ways of defining and interacting with sub-graphs, more flexible ways of manipulating variables, built-in variables and meta-data, and better ways of defining&amp;nbsp;characters.&lt;/p&gt;
&lt;h2&gt;Update: Pure&amp;nbsp;Signals&lt;/h2&gt;
&lt;p&gt;It occurred to me after posting this that there is an even easier way to achieve this - as long as you don&amp;#8217;t need to keep any state in the signalling object. Because Godot 4 allows you to pass signals and callables, you could just pass the signal to proceed with the signal that initiates the action. When the consumer is ready to proceed they can then just call &lt;code&gt;emit&lt;/code&gt; on&amp;nbsp;it.&lt;/p&gt;
&lt;p&gt;I might still prefer the other way of doing it because &lt;code&gt;_current_process.proceed()&lt;/code&gt; reads a little better than &lt;code&gt;_current_process_proceed_signal.emit()&lt;/code&gt;.&lt;/p&gt;</content><category term="Game Development"></category><category term="godot"></category><category term="gdscript"></category><category term="godot4"></category></entry><entry><title>A Culture of Conspiracy</title><link href="https://blog.hyperlinkyourheart.com/culture-of-conspiracy.html" rel="alternate"></link><published>2023-05-21T17:56:00+02:00</published><updated>2023-05-21T23:08:00+02:00</updated><author><name>Kevin Houlihan</name></author><id>tag:blog.hyperlinkyourheart.com,2023-05-21:/culture-of-conspiracy.html</id><summary type="html">&lt;p&gt;A book about&amp;nbsp;nonsense.&lt;/p&gt;</summary><content type="html">&lt;p&gt;I read &lt;a href="https://www.amazon.com/Culture-Conspiracy-Apocalyptic-Contemporary-Comparative-ebook/dp/B00DNJD46C/ref=sr_1_1?dchild=1&amp;amp;keywords=a+culture+of+conspiracy&amp;amp;qid=1604350628&amp;amp;sr=8-1" title="A Culture of Conspiracy on Amazon"&gt;&amp;#8220;A Culture of Conspiracy&amp;#8221;&lt;/a&gt; by Professor Michael Barkun a few years ago. In it, he describes several broad categories of conspiracist belief, and traces the development of a variety of beliefs from their origins through to the time of writing as they branch and mutate and&amp;nbsp;recombine.&lt;/p&gt;
&lt;p&gt;I started this post not too long after reading it but I&amp;#8217;ve let it languish for several years now. I hope I&amp;#8217;m not misrepresenting its contents due to my failing memory, but I would recommend you read it yourself and find out - it&amp;#8217;s definitely worthwhile, and I&amp;#8217;m only picking out a few bits of it to talk about that were most interesting to me&amp;nbsp;personally.&lt;/p&gt;
&lt;h2&gt;Conspiracy&amp;nbsp;Types&lt;/h2&gt;
&lt;p&gt;Barkun identifies three types of conspiracy theories based on their&amp;nbsp;scope:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Event conspiracies&lt;/strong&gt; - limited to a specific event, such as the assassination of &lt;span class="caps"&gt;JFK&lt;/span&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Systemic conspiracies&lt;/strong&gt; - concern the plans of a specific organisation or group with broad goals, such as taking over the&amp;nbsp;world.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Superconspiracies&lt;/strong&gt; - this type of conspiracy links together various other conspiracies of the &lt;em&gt;event&lt;/em&gt; and &lt;em&gt;systemic&lt;/em&gt; varieties in a hierarchical manner (e.g. the &lt;span class="caps"&gt;CIA&lt;/span&gt; assassinated &lt;span class="caps"&gt;JFK&lt;/span&gt;, but the &lt;span class="caps"&gt;CIA&lt;/span&gt; are a tool of the Illuminati and his assassination served their purposes, but &lt;em&gt;really&lt;/em&gt; the Illuminati are in thrall to Satan&amp;nbsp;etc.)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Mostly this makes me wonder if systemic or event conspiracies ever really exist on their own in anybody&amp;#8217;s mind anymore, because they all seem to espouse and nod along with such a mish-mash of ideas. He does note that superconspiracies have been on the rise since the 1980s. But is something like QAnon even separable from the web of conspiracist ideas in which it seems to be&amp;nbsp;embedded?&lt;/p&gt;
&lt;h2&gt;Stigmatised&amp;nbsp;Knowledge&lt;/h2&gt;
&lt;p&gt;Barkun identifies the origin of conspiracist thinking in &lt;em&gt;stigmatised knowledge&lt;/em&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;By stigmatized knowledge I mean claims to truth that the claimants regard as verified despite the marginalization of those claims by the institutions that conventionally distinguish between knowledge and&amp;nbsp;error.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Stigmatised knowledge is not exclusive to conspiracism, but it is inevitably a feature of it, and I think it leads to it readily even when it initially exists without it. For example a believer in a discredited alternative medical treatment might not initially believe in any specific conspiracy in connection to it, but eventually they will have to explain why it is not accepted into the mainstream, and an easy answer is that it is being suppressed by a conspiracy of insiders who benefit from it not being&amp;nbsp;adopted.&lt;/p&gt;
&lt;p&gt;With this concept in mind it is easy to understand the origin of &amp;#8220;pipelines&amp;#8221; into conspiracism from any pseudoscientific field, fringe religious movement, or even from political ideologies that see their righteousness as obvious and their victory as inevitable. When their distorted worldview meets reality and they have to explain their failures, conspiracism is right there. Once they&amp;#8217;re understanding one piece of stigmatised knowledge as being suppressed by a conspiracy, it&amp;#8217;s a small step to accepting other such&amp;nbsp;claims.&lt;/p&gt;
&lt;h2&gt;Fact is Fiction, Fiction is&amp;nbsp;Fact&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;The commonsense distinction between fact and fiction melts away in the conspiracist world. More than that, the two exchange places, so that in striking ways conspiracists often claim first that what the world at large regards as fact is actually fiction, and second that what seems to be fiction is really&amp;nbsp;fact.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Anybody who has spent any time listening to conspiracists will recognise the truth of this statement right&amp;nbsp;away.&lt;/p&gt;
&lt;p&gt;First of all, all conspiracy theories necessarily involve claims that one or more generally accepted truths are actually lies intended to pacify or deceive the sheep. Often any information coming from an institution is dismissed without consideration, because it is a given that institutions - be they governments, universities or the &amp;#8220;mainstream media&amp;#8221; - are &amp;#8220;in on&amp;nbsp;it&amp;#8221;.&lt;/p&gt;
&lt;p&gt;With fact rendered fictional, it becomes very easy to point to fiction to fill gaps in their evidence. Sometimes this takes the form of taking fictional sources as literal accounts, and Barkun describes some of these instances. Other times fictional stories are said to contain encoded messages or to be for the purpose of softening up the masses to accept some coming revelation or societal change - nothing can ever be somebody&amp;#8217;s neat idea for a sci-fi concept, or allegory, or their opinion of where society is or is going,&amp;nbsp;unplanned.&lt;/p&gt;
&lt;p&gt;In some accounts, I believe that describing their literal plans in fiction is believed to have an occult purpose for the conspirators, much like is claimed about symbols on currency or on buildings (e.g. Denver airport). Advertising their plans in a way that will only be understood by an enlightened few is somehow a part of bringing them to fruition. This is how Alex Jones is interpreting H.G Wells&amp;#8217; &amp;#8220;Time Machine&amp;#8221; when he talks about Eloi and Morlocks, saying &amp;#8220;it&amp;#8217;s all right there folks&amp;#8221; - and similarly for the other pop cultural works he references, of&amp;nbsp;course.&lt;/p&gt;
&lt;h2&gt;Emergency&amp;nbsp;Management&lt;/h2&gt;
&lt;p&gt;One of the things I was most eager to learn about from this book was &lt;span class="caps"&gt;FEMA&lt;/span&gt; camp conspiracy theories. I find these theories amongst the most frustrating (and amusing) because of how they look past the very real historical precedents of concentration camps, and the present day realities of mass incarceration and political repression in the United States and elsewhere, and focus instead on a long running conspiracy that is always just on the cusp of rounding up those troublesome&amp;nbsp;&amp;#8220;patriots&amp;#8221;.&lt;/p&gt;
&lt;p&gt;Of course, the longer this conspiracy has been in the milieu the more absurd it becomes. Barkun identifies the origins of this theory as a pamphlet by a man named William Pabst, written sometime prior to 1979. Pabst warns: &amp;#8220;your country and way of life [will be] replaced by a system in which you will be a slave in a concentration&amp;nbsp;camp&amp;#8221;.&lt;/p&gt;
&lt;p&gt;As such, more recent incarnations of this theory imply that the &lt;span class="caps"&gt;US&lt;/span&gt; government (acting on behalf of some hidden puppet-masters, perhaps) has been building and maintaining a network of secret camps for &lt;em&gt;over 40 years&lt;/em&gt; without ever putting their nefarious plans into&amp;nbsp;motion!&lt;/p&gt;
&lt;p&gt;Historical instances of the use of internment and concentration camps by governments are of course very real. However, they have never required such extensive periods of preparation. When the British government decided to round up Irish Nationalists in Northern Ireland, they built temporary structures in the weeks prior to doing so, and more permanent structures over the next few years after that. The United States forced 120,000 Japanese Americans into camps during &lt;span class="caps"&gt;WWII&lt;/span&gt;, first in hastily converted racetracks and fairgrounds, and then in more permanent facilities built over a few months in 1942. Even the horrifying machinery of the Nazis did not require decades to construct, instead comprising a mix of repurposed buildings of many types, and camps newly constructed during the course of the war - a system that imprisoned and exterminated&amp;nbsp;millions.&lt;/p&gt;
&lt;h2&gt;The Speed of&amp;nbsp;Lies&lt;/h2&gt;
&lt;p&gt;When this book was first published in 2003 it had already been updated from the largely completed manuscript to include chapters concerning the explosion of conspiracism after the 9/11 attacks. The second edition, published in 2013, which is the one I read, had been updated with chapters about birtherism and millenarian conspiracies about the year&amp;nbsp;2012.&lt;/p&gt;
&lt;p&gt;In a testament to the veracity of the saying that &amp;#8220;a lie can travel half-way around the world while the truth is putting its shoes on&amp;#8221;, many of the conspiracies considered in the book, even the later additions, seem quaint and out-of-date from the vantage point of 2023. Of course any book on the constantly shifting, slippery world of conspiracism will be out-of-date (in some ways) within a few years of coming&amp;nbsp;out.&lt;/p&gt;
&lt;p&gt;Nonetheless, the analysis is still useful to understanding the process of the creation and dissemination of conspiracist ideas. Indeed there is no amount of time and lack of confirmation that will kill many conspiracies - the reason I was so focused on &lt;span class="caps"&gt;FEMA&lt;/span&gt; camp conspiracies in this post was because somebody told me just a few years ago that Hillary Clinton would have put everybody in camps, and similar rhetoric arose even more recently when the language around &lt;span class="caps"&gt;COVID&lt;/span&gt;-19 mitigation measures was claimed to be intended to &amp;#8220;make us feel like we&amp;#8217;re in prison&amp;#8221; - a &lt;em&gt;&lt;span class="caps"&gt;FEMA&lt;/span&gt; camp of the mind&lt;/em&gt; I&amp;nbsp;guess.&lt;/p&gt;
&lt;p&gt;The only writing of Barkun&amp;#8217;s that I&amp;#8217;ve read concerning more recent developments in the conspiracy sphere is an &lt;a href="https://foreignpolicy.com/2018/11/08/failed-prophecies-wont-stop-trumps-true-believers/" title="Michael Barkun on QAnon"&gt;article in &amp;#8220;Foreign Policy&amp;#8221;&lt;/a&gt; about QAnon which examines the efforts of its adherents to cope with its failed prophecies. As far as I&amp;#8217;m aware QAnon is still going strong despite its predictive&amp;nbsp;failures.&lt;/p&gt;
&lt;p&gt;QAnon has been described by some as a &amp;#8220;big tent&amp;#8221; conspiracy theory because of its ability to adapt and incorporate new claims. However, it&amp;#8217;s hardly unique in that regard - &lt;span class="caps"&gt;NWO&lt;/span&gt; conspiracy theories and many others have been interpreting events through their particular lenses and adapting and incorporating new claims for decades. QAnon might be unique in terms of its longevity &lt;em&gt;despite having made specific, dated predictions that failed to come to pass&lt;/em&gt;, but to me it seems more like a systemic conspiracy that conspiracists have been rolling into their own long-existing superconspiracies. It only seems like QAnon is the &amp;#8220;big tent&amp;#8221; because it broke so spectacularly into the mainstream. As it breaks down under the weight of its failures it seems like it is adapting to include other theories, but it is actually the other theories that are absorbing it into themselves and trying to salvage the parts of it that are&amp;nbsp;useful.&lt;/p&gt;
&lt;h3&gt;More&amp;nbsp;Conspiracism&lt;/h3&gt;
&lt;p&gt;I started listening to the &lt;a href="https://knowledgefight.libsyn.com/" title="Knowledge Fight Podcast"&gt;Knowledge Fight&lt;/a&gt; podcast during Alex Jones&amp;#8217; defamation trials to get the scoop on developments, and I haven&amp;#8217;t been able to stop listening since. Dan and Jordan&amp;#8217;s analysis of Jones&amp;#8217; bullshit is excellent, and it&amp;#8217;s a great way of keeping up with what he&amp;#8217;s saying. It&amp;#8217;s also incredibly&amp;nbsp;entertaining.&lt;/p&gt;
&lt;p&gt;I have been meaning to check out the &lt;a href="https://soundcloud.com/qanonanonymous" title="QAnon Anonymous Podcast"&gt;QAnon Anonymous podcast&lt;/a&gt; as well for a while to get a more general view, but I haven&amp;#8217;t gotten around to it&amp;nbsp;yet.&lt;/p&gt;</content><category term="Books"></category><category term="non-fiction"></category><category term="politics"></category></entry><entry><title>Syncthing Update</title><link href="https://blog.hyperlinkyourheart.com/syncthing.html" rel="alternate"></link><published>2023-04-08T23:15:00+02:00</published><updated>2023-04-08T23:15:00+02:00</updated><author><name>Kevin Houlihan</name></author><id>tag:blog.hyperlinkyourheart.com,2023-04-08:/syncthing.html</id><summary type="html">&lt;p&gt;Joplin sync didn&amp;#8217;t work out long term, but I&amp;#8217;m still using&amp;nbsp;Syncthing&lt;/p&gt;</summary><content type="html">&lt;p&gt;In a &lt;a href="https://blog.hyperlinkyourheart.com/joplin.html" title="Previous Joplin &amp;amp; Syncthing post"&gt;previous instalment&lt;/a&gt;, I described how I used &lt;a href="https://syncthing.net/" title="Syncthing website"&gt;Syncthing&lt;/a&gt; to sync my notes in &lt;a href="https://joplinapp.org/" title="Joplin's website"&gt;Joplin&lt;/a&gt; on my laptop to Joplin on my phone. Unfortunately that arrangement didn&amp;#8217;t last long - updates to different versions of Joplin on different devices resulted in incompatible versions of the notes database being synced, and at one point the Android version became unable to export to the filesystem at all. I never got to the bottom of that, but I had moved to using &lt;a href="https://logseq.com/" title="Logseq website"&gt;Logseq&lt;/a&gt; for most of my notes on the computer anyway, so it didn&amp;#8217;t really matter&amp;nbsp;much.&lt;/p&gt;
&lt;p&gt;I also mostly fell out of using Syncthing, since I no longer required it for its primary purpose. However I got a Framework laptop recently and had the need to sync my Logseq graphs to it, as well as my music collection, work files, etc. It was such a joy to get it set up and watch files start to zip across to the new machine that I once again had to sing the praises of this amazing piece of&amp;nbsp;software.&lt;/p&gt;
&lt;p&gt;Some shares I have set up so&amp;nbsp;far:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;The camera roll on my phone&lt;/strong&gt; - send only so that remote devices can&amp;#8217;t add or delete photos. It&amp;#8217;s great to have photos zip across to whichever computer I&amp;#8217;m using without having to involve Google&amp;nbsp;Photos.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The Default sync folder&lt;/strong&gt; - why not? If some random file is needed everywhere it can go in there. It always throws me a bit that these create a common shared folder rather than each device&amp;#8217;s folder being it&amp;#8217;s own thing, but it&amp;#8217;s cool, it&amp;#8217;s fine, I&amp;#8217;ll get used to&amp;nbsp;it.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;My Logseq graph folders&lt;/strong&gt; - I set these up to backup any changes just in case, because Syncthing will not perform merges. However I&amp;#8217;m not really that worried about it because I will only work on one machine at a time, and if the sync runs regularly it shouldn&amp;#8217;t be a&amp;nbsp;problem.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;My music collection&lt;/strong&gt; - I set this up to ignore &lt;code&gt;*.zip&lt;/code&gt; and &lt;code&gt;*.part&lt;/code&gt; for uh&amp;#8230; reasons. One of the nuisances of a collection of downloaded music is ensuring that every new acquisition gets to every device where you might want to listen to it &lt;em&gt;before&lt;/em&gt; you want to listen to it. Well, problem solved! And no more shaming from Spotify for streaming the same album on repeat for several months! (It was Manu&amp;nbsp;Chao)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Several shares specifically between two particular devices&lt;/strong&gt; e.g. huge desktop replacement laptop to the Framework, for when I want to share files specifically between those but not my&amp;nbsp;phone.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I&amp;#8217;m considering syncing some specific work folders as well so I can more easily untether from my desk, but&amp;#8230; haven&amp;#8217;t decided&amp;nbsp;yet.&lt;/p&gt;</content><category term="Technology"></category><category term="foss"></category><category term="privacy"></category></entry><entry><title>The Ancaps</title><link href="https://blog.hyperlinkyourheart.com/the-ancaps.html" rel="alternate"></link><published>2022-09-03T18:26:00+02:00</published><updated>2022-09-03T18:26:00+02:00</updated><author><name>Kevin Houlihan</name></author><id>tag:blog.hyperlinkyourheart.com,2022-09-03:/the-ancaps.html</id><summary type="html">&lt;p&gt;&lt;span class="caps"&gt;HBO&lt;/span&gt;&amp;#8217;s &amp;#8220;The Anarchists&amp;#8221; is not really about&amp;nbsp;Anarchists&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;img alt="Screenshot of a couple embracing in front of a bonfire, into which anarcho-capitalists are throwing books produced by a government" src="https://blog.hyperlinkyourheart.com/images/the-ancaps/bookburning.jpg" title="What if we kissed at the Anarchist book burning?"&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.vice.com/en/article/m7gvdp/real-anarchists-react-to-the-anarchists-a-new-series-about-crypto-bros"&gt;Much has been said already&lt;/a&gt; about the fact that &lt;span class="caps"&gt;HBO&lt;/span&gt;&amp;#8217;s documentary series &amp;#8220;The Anarchists&amp;#8221; is not really about anarchists, and by &lt;a href="https://twitter.com/magpiekilljoy/status/1547566126174941186"&gt;people far more capable of making the argument&lt;/a&gt; than I. Nonetheless, I do have some thoughts on that and other aspects of the&amp;nbsp;documentary.&lt;/p&gt;
&lt;p&gt;My overall impression of the documentary is that it is philosophically vacuous and insincere. &amp;#8220;Anarchism&amp;#8221; is defined superficially by the characters in what is essentially an examination of interpersonal drama. The history of Anarchism proper, and its inherent conflict with capitalism is not explored, but neither, really, is &amp;#8220;anarcho&amp;#8221;-capitalism or the ideas behind it, &lt;a href="https://tommullentalksfreedom.com/featured/where-is-the-anarchism-in-hbos-the-anarchists/"&gt;on their own terms&lt;/a&gt; or otherwise. The community is simply mined for drama and spectacle. The main propaganda points of the doc lie in the fact that &amp;#8220;freedom&amp;#8221; is just implicitly associated with laissez-faire capitalism, and the appropriation of the word &amp;#8220;Anarchism&amp;#8221; and anarchist symbols by the right, &lt;a href="https://www.goodreads.com/quotes/3194162-one-gratifying-aspect-of-our-rise-to-some-prominence-is"&gt;a long-running project&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;The Inherent Contradictions of&amp;nbsp;Ancapitalism&lt;/h2&gt;
&lt;p&gt;Though the documentary has little interest in examining them, the cracks and contradictions in the ideology do show&amp;nbsp;through.&lt;/p&gt;
&lt;p&gt;I think it is safe to say that everybody who enthusiastically embraces an extreme capitalist ideology thinks that they are, or will be, the boss of whatever enterprise they are involved in. Of course this produces tension when it turns out that somebody&amp;#8217;s property rights, and a lack of any critique of property or the hierarchies it produces, makes subordinates of people who consider themselves entitled to be in&amp;nbsp;charge.&lt;/p&gt;
&lt;p&gt;The Anarchapulco conference that the documentary focuses on was not organised in a non-hierarchical manner from its conception because anarcho-capitalism does not renounce all hierarchies, only the existence of the state. &lt;a href="https://itsgoingdown.org/it-looks-like-hitler-was-pretty-good-hbo/"&gt;Jeff Berwick&lt;/a&gt;, the founder and apparent &amp;#8220;owner&amp;#8221; of the conference behaves throughout as if organising the conference is something that an employee should be doing on his behalf, with his own role limited to giving a keynote, receiving adulation, and&amp;nbsp;partying.&lt;/p&gt;
&lt;p&gt;The first such employee we are introduced to is Nathan Freeman, who apparently had a leading role in organising the conference for several years after attending the initial one. It seems to me that Freeman thought himself and Berwick were partners in the endeavour. Berwick obviously saw things differently, and replaced Freeman in 2019 with an&amp;nbsp;outsider.&lt;/p&gt;
&lt;p&gt;Tragically, it seems like Freeman couldn&amp;#8217;t cope with this humiliation, and essentially drank himself to death. Berwick didn&amp;#8217;t even offer condolences to his family, because he&amp;#8217;s an enormous piece of&amp;nbsp;shit.&lt;/p&gt;
&lt;p&gt;There are a number of aspects to the circumstances of his death that a documentary that was an honest examination of anarcho-capitalism would interrogate. He fell victim to a crypto scam shortly before becoming sick. What is the anarcho-capitalist perspective on this kind of crime? What does history tell us about private money and its effects on society? It&amp;#8217;s glossed over as an unfortunate, unavoidable risk of &amp;#8220;freedom&amp;#8221;. He had no insurance, and his family had to rely on charity to pay his medical bills. What is the anarcho-capitalist perspective on the provision of healthcare? The question is not even&amp;nbsp;asked.&lt;/p&gt;
&lt;p&gt;Interestingly, John and Lily, the young couple who flee to Mexico after being arrested on drug traficking charges, do form a critique of the hierarchical, commercial nature of the Anarchapulco conference, and start their own alternative conference called Anarchaforko. It&amp;#8217;s a bit unclear the extent to which this is organised at all rather than just people showing up and doing whatever, but it seems to work, and I would love to hear more about how this fits with their apparent objectivist leanings. But of course we get nothing like&amp;nbsp;that.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Screenshot of Lily Forester post on Facebook: &amp;quot;This conference was supposed to be for ancaps by ancaps!&amp;quot;" src="https://blog.hyperlinkyourheart.com/images/the-ancaps/forancaps.jpg" title="Well that's yer problem right there..."&gt;&lt;/p&gt;
&lt;h2&gt;Stateless in&amp;nbsp;Mexico&lt;/h2&gt;
&lt;p&gt;Probably the funniest aspect of the documentary for me is that the participants seem to think that Mexico is &amp;#8220;more anarchist&amp;#8221; than the &lt;span class="caps"&gt;US&lt;/span&gt;, just based on the general vibes. Mexico, of course, does have a state, and I don&amp;#8217;t have any reason to think that it is &amp;#8220;less of&amp;#8221; a state than that of the &lt;span class="caps"&gt;US&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;I think this sense of &amp;#8220;anarchiness&amp;#8221; is probably the result of a few different factors. Many of the ancap immigrants are relatively wealthy, and apparently speak little or no Spanish. They are essentially just squatting on top of Mexican society, with no real connections to it, and using their wealth to extract what they need from it. The Mexican state protects them, as states generally protect the wealthy. They have little negative contact with it, and don&amp;#8217;t hear about other people&amp;#8217;s negative interactions with it as they would in the &lt;span class="caps"&gt;US&lt;/span&gt;, because they don&amp;#8217;t speak the language. They&amp;#8217;re just living in a little fantasy colonialist&amp;nbsp;bubble.&lt;/p&gt;
&lt;p&gt;Some members of the community are not so well off, and they do have negative experiences with the Mexican state, ranging from dealing with bureaucracy to being pursued, threatened and arrested by the&amp;nbsp;police.&lt;/p&gt;
&lt;p&gt;Although Lily Forester is a member of the latter group, it is her concluding statement on the existence of the state that best sums up the general&amp;nbsp;attitude:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I just want to be left alone, like, a state can exist if it&amp;#8217;s going to leave me&amp;nbsp;alone.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;On a personal level I can relate, especially given what she went through, but it&amp;#8217;s a far cry from the moral clarity of this Fannie Lou Hamer&amp;nbsp;quote:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Nobody’s free until everybody’s&amp;nbsp;free.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Any meaningful conception of freedom can&amp;#8217;t ignore that other people are subject to repression or exploitation, but that is exactly what these ancaps constantly do - the Mexican state is fine because I&amp;#8217;m a rich foreigner and it leaves me alone, capitalist hierarchies are fine because I&amp;#8217;m on top of&amp;nbsp;them.&lt;/p&gt;
&lt;h2&gt;M&amp;#8217;Aidez!&lt;/h2&gt;
&lt;p&gt;As I mentioned above, the primary participants in the documentary fall roughly into two groups - one comprised of relatively wealthy entrepeneurs like Berwick and the Freemans, and the other of struggling working class people like Lily Forester and John Galton, Jason Henza, and Paul&amp;nbsp;Propert.&lt;/p&gt;
&lt;p&gt;Though both groups are motivated by more-or-less the same ideology (&lt;a href="https://twitter.com/jasonhenza/status/1559305420816220160"&gt;Henza claims himself and Forester are not ancaps&lt;/a&gt;, but I don&amp;#8217;t really see much distinction between anarcho-capitalism and voluntaryism or agorism myself), the differences between their circumstances is stark. The wealthy run their businesses from their lavish properties while the rest do odd jobs, deal drugs, and otherwise hustle to survive while living in marginal circumstances. As &lt;a href="https://bennorton.com/thaddeus-russel-s-right-wing-libertarian-historical-revisionism/"&gt;Thaddeus Russell&lt;/a&gt;&amp;nbsp;notes:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;It&amp;#8217;s very easy to escape governments, banks and states if you&amp;#8217;re already a Bitcoin millionaire. If you&amp;#8217;re like John and Lily, you&amp;#8217;ve got no resources, nothing, it&amp;#8217;s hard, it turns out, and dangerous, in fact, to be an anarchist in&amp;nbsp;Mexico.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The tension between these two groups is discussed at several points. The drug dealing and other illegal activity (like the theft of a Bitcoin &lt;span class="caps"&gt;ATM&lt;/span&gt;) are an inconvenience for the wealthy, and the unhinged Paul Propert is a potentially deadly threat to everybody, but they have no solutions. Everybody is just on their own to fend for&amp;nbsp;themselves.&lt;/p&gt;
&lt;p&gt;The documentary explores the backgrounds of Galton, Forester and Propert in some detail and finds a variety of broken homes, substance abuse problems, and other traumas. Like the characters themselves, it doesn&amp;#8217;t seem to consider for a moment that the source of these traumas is the very social system that they cling to so&amp;nbsp;tightly.&lt;/p&gt;
&lt;p&gt;Nonetheless the clearest critique the documentary has for the anarcho-capitalist project is the lack of solidarity and support that those lacking means, and in dangerous circumstances, receive from the community, and what this would imply for an anarcho-capitalist society. Erika Harris, who ends up feeling alienated from the community and leaving Acapulco for Belize, makes this plea for mutual aid after John Galton is murdered, and Lily and Jason are on the&amp;nbsp;run:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;There&amp;#8217;s an emergency among us, how will we respond? With shelter, with safehouses, with passage over borders if necessary &amp;#8230; We need each other to get this done. I mean, we need each other just to move one inch&amp;nbsp;forward.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Unfortunately, her plea seems to have fallen on deaf&amp;nbsp;ears.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Jeff Berwick setting a printout of an American flag on fire with a 100 Bolivar note" src="https://blog.hyperlinkyourheart.com/images/the-ancaps/bolivars.jpg" title="Vuvuzela iPhone Death to America"&gt;&lt;/p&gt;</content><category term="TV"></category><category term="politics"></category><category term="anarchism"></category><category term="capitalism"></category><category term="socialism"></category></entry><entry><title>Nimpressions</title><link href="https://blog.hyperlinkyourheart.com/nimpressions.html" rel="alternate"></link><published>2022-05-26T20:16:00+02:00</published><updated>2022-05-26T20:16:00+02:00</updated><author><name>Kevin Houlihan</name></author><id>tag:blog.hyperlinkyourheart.com,2022-05-26:/nimpressions.html</id><summary type="html">&lt;p&gt;First impressions of the Nim programming&amp;nbsp;language&lt;/p&gt;</summary><content type="html">&lt;p&gt;Python is my go-to language for personal projects, and even client projects when I can get away with it (though usually those are Windows based and within the .Net ecosystem, so I stick with C#). However, it often gives me pause to be using one of the slowest and &lt;a href="https://thenewstack.io/which-programming-languages-use-the-least-electricity/" title="Programming languages energy efficiency"&gt;least energy efficient&lt;/a&gt; languages available - I might do another post about that, but suffice it to say that it doesn&amp;#8217;t align with my values to needlessly waste&amp;nbsp;resources.&lt;/p&gt;
&lt;p&gt;The ideal would be a language that&amp;#8217;s as easy to write as Python, but as fast and energy efficient as C, or close to it. Well recently I came across a language that claims be both of those things: &lt;a href="https://nim-lang.org/" title="Nim homepage"&gt;Nim&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I put together a simple command line application (named &lt;a href="https://github.com/khoulihan/luz" title="Luz on GitHub"&gt;Luz&lt;/a&gt;) in Nim this week in order to try it out. Appropriately enough given my reason for trying Nim, it just shows the current electricity rate band, and optionally a chart, because where I live there are two peak periods during the day when it is better not to do anything power-intensive. I went on to make a start on a very simple Gemini server called Sparkle, which is still a &lt;span class="caps"&gt;WIP&lt;/span&gt;. Here are some of my thoughts on the experience as a mediocre developer with some Python and C#&amp;nbsp;experience.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/khoulihan/luz" title="Luz on GitHub"&gt;&lt;img alt="Luz in action" src="https://blog.hyperlinkyourheart.com/images/nimpressions/luz_screenshot.png" title="Going from bad to worse"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;choosenim&lt;/h2&gt;
&lt;p&gt;Nim has a &lt;a href="https://github.com/dom96/choosenim" title="choosenim on GitHub"&gt;tool for installing its toolchain&lt;/a&gt; and and switching between different versions of the compiler, similar to pyenv. Unfortunately it didn&amp;#8217;t work for me on Pop! &lt;span class="caps"&gt;OS&lt;/span&gt; 22.04 due to it having too new a version of libssl. I was able to install the Nim compiler manually easily enough by just downloading the tarball and copying the contents to an appropriate location, and then adding the &lt;code&gt;bin&lt;/code&gt; directory to my path. There was an install script in the tarball but it didn&amp;#8217;t copy everything for some&amp;nbsp;reason.&lt;/p&gt;
&lt;p&gt;Not a great start, and I&amp;#8217;m not sure what I&amp;#8217;m missing out on by not using choosenim, but I can figure that out later if I continue using the&amp;nbsp;language.&lt;/p&gt;
&lt;h2&gt;Typing&lt;/h2&gt;
&lt;p&gt;Static typing is something I&amp;#8217;m well used to from C# of course, but I don&amp;#8217;t engage with Python&amp;#8217;s type hinting at all. There is type inference in many situations, and many familiar collection types such as sets, tables, sequences and tuples which are as convenient to instantiate as their Python equivalents, though of course you can&amp;#8217;t mix unrelated types within them (aside from tuples)(and why would you do that anyway, you monster). Mostly it is just convenient to know at compile time where there are type mismatches, rather than hearing about them at runtime or just getting weird&amp;nbsp;behaviour.&lt;/p&gt;
&lt;p&gt;Nim is only very minimally object-oriented. There is inheritance, but not multiple-inheritance, mixins, or anything resembling the interfaces or traits of other languages. This is probably one of the most concerning aspects of the language for me. It seems like it will inevitably lead to repeated code at some point if procedures can&amp;#8217;t accept abstract interfaces as input instead of concrete&amp;nbsp;types.&lt;/p&gt;
&lt;p&gt;On the other hand I try to steer away from an object-oriented style in Python unless it really makes sense for the problem I&amp;#8217;m working on. In Luz, the classes I created were little more than structs, with no inheritance required, and that&amp;#8217;s perfectly sufficient for many&amp;nbsp;problems.&lt;/p&gt;
&lt;p&gt;There are also apparently &lt;a href="https://github.com/yglukhov/iface" title="iface library on GitHub"&gt;libraries&lt;/a&gt; that create a means to specify interfaces using meta-programming, but that&amp;#8217;s not something I&amp;#8217;ve explored&amp;nbsp;yet.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;Holiday&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;ref&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;object&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;DateTime&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;localName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;string&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;string&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;countryCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;string&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;fixed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;bool&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;global&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;bool&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;counties&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Option&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;seq&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;string&lt;/span&gt;&lt;span class="o"&gt;]]&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;launchYear&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Option&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;


&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;holidays&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;initTable&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;seq&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Holiday&lt;/span&gt;&lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;


&lt;span class="k"&gt;proc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;isHoliday&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;DateTime&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c"&gt;# This will occur if API key was not provided&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;not&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;holidays&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hasKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;year&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;holidays&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;year&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c"&gt;# global indicates that the holiday applies to the whole country&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;global&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;yearday&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;yearday&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;break&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Uniform Function Call&amp;nbsp;Syntax&lt;/h2&gt;
&lt;p&gt;This is really neat - any procedure or function can be called as if it is a method of the type of its first&amp;nbsp;parameter.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;proc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;sendErrorResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;requestSocket&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;AsyncSocket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;StatusCode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;string&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sx"&gt;{.async.}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;await&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;requestSocket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;{ord(code)} {meta}&lt;/span&gt;&lt;span class="se"&gt;\r\L&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;proc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;processRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;requestSocket&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;AsyncSocket&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sx"&gt;{.async.}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c"&gt;# These calls are equivalent&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;await&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;requestSocket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sendErrorResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;StatusCode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;notFound&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Not Found&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;await&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sendErrorResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;requestSocket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;StatusCode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;notFound&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Not Found&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This means that any type can be &amp;#8220;extended&amp;#8221; in a sense just by writing procedures with that type as the first parameter, no need for sub-classing or a special extension method&amp;nbsp;syntax.&lt;/p&gt;
&lt;h2&gt;Blocks&lt;/h2&gt;
&lt;p&gt;One neat little feature is that you can open a new code block anywhere, with or without a name, and as well as being visually separated from the code around it it will have its own scope. A &lt;code&gt;break&lt;/code&gt; statement will break out of that block, but not the containing&amp;nbsp;one.&lt;/p&gt;
&lt;p&gt;I didn&amp;#8217;t find much use for this in either of the projects I&amp;#8217;ve worked on so far, but it&amp;#8217;s definitely something I can see being useful for longer procedures and certain control-flow&amp;nbsp;situations.&lt;/p&gt;
&lt;h2&gt;Closures&lt;/h2&gt;
&lt;p&gt;Nim supports passing around references to procedures, which allows for a number of neat constructs, including closures. The below procedure creates a closure that animates a spinner when called in a loop while waiting for an &lt;span class="caps"&gt;IO&lt;/span&gt; operation to conclude. It contains everything it needs, including a&amp;nbsp;constant.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;proc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;getDisplayProgressClosure&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;proc&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;phases&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;🮪&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;🮫&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;🮭&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;🮬&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;lastTime&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;phase&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;initial&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;proc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;displayProgress&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;elapsed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;lastTime&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;elapsed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;inMilliseconds&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;or&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;initial&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;lastTime&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;not&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;initial&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;erasePrevious&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;initial&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;styledEcho&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;fgGreen&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;{phases[phase]}&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;fgCyan&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="s"&gt;&amp;quot; Retrieving holidays...&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;inc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;phase&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;phase&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;phases&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;high&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;phase&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;displayProgress&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Templates &lt;span class="amp"&gt;&amp;amp;&lt;/span&gt; Compile Time&amp;nbsp;Execution&lt;/h2&gt;
&lt;p&gt;One of the most exciting features of Nim, for me, is the ability to execute code at compile time, and otherwise manipulate the final state of the&amp;nbsp;code.&lt;/p&gt;
&lt;p&gt;For example to embed a file in a binary in C# you have to set a property against the file in the &lt;span class="caps"&gt;IDE&lt;/span&gt; (or maybe in the project file) to make it an embedded resource, and then do some reflection to pull it back out at runtime. In Nim, you can just call &lt;code&gt;readFile&lt;/code&gt; and assign the result to a&amp;nbsp;constant.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;DEFAULT_BANDS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;readFile&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;./config/bands.json&amp;quot;&lt;/span&gt;
&lt;span class="k"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;DEFAULT_CONFIG&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;readFile&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;./config/luz.toml&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;There is also a compile-time branching statement, &lt;code&gt;when&lt;/code&gt;. This is similar to the pre-processor &lt;code&gt;#if&lt;/code&gt; in C#, or &lt;code&gt;#ifdef&lt;/code&gt; in C, but it fits more naturally with the rest of the&amp;nbsp;code.&lt;/p&gt;
&lt;p&gt;Templates allow you to insert specified code in other parts of the codebase, with substitutions, before compilation. One use for this is as an alternative to short procedures, so the code gets inlined, saving a function&amp;nbsp;call.&lt;/p&gt;
&lt;p&gt;I feel like I&amp;#8217;m only at the start of getting my head around this feature. I thought it might be a good way to output variations of a procedure for operating on different types, but I&amp;#8217;m not sure the result is readable or concise enough to be&amp;nbsp;worthwhile:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;template&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;createGetSetting&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;valueType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;untyped&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;argValueTypeGet&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;untyped&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;envValueTypeGet&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;untyped&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;confValueTypeGet&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;untyped&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;proc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;getSetting&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Table&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;arg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;conf&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;TomlValueRef&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;confSection&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;confKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;valueType&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;valueType&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ConfigVariableSource&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ConfigVariableSource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Default&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;arg&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;arg&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;kind&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;vkNone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="n"&gt;argValueTypeGet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;arg&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="n"&gt;ConfigVariableSource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CommandLine&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;envStr&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;getEnv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;envStr&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;envValueTypeGet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;envStr&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;ConfigVariableSource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Environment&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;conf&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;confSection&lt;/span&gt;&lt;span class="o"&gt;][&lt;/span&gt;&lt;span class="n"&gt;confKey&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;confValueTypeGet&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;ConfigVariableSource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ConfigFile&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;proc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;splitOnComma&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;val&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;seq&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;string&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;val&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sc"&gt;&amp;#39;,&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;proc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;getStringSequence&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;TomlValueRef&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;seq&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;string&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getElems&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;@[]&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getStr&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;


&lt;span class="k"&gt;proc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;parseIntArg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;val&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;parseInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;$&lt;/span&gt;&lt;span class="n"&gt;val&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="n"&gt;createGetSetting&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;`&lt;/span&gt;&lt;span class="o"&gt;$&lt;/span&gt;&lt;span class="p"&gt;`,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;`&lt;/span&gt;&lt;span class="o"&gt;$&lt;/span&gt;&lt;span class="p"&gt;`,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;getStr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;createGetSetting&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;parseIntArg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;parseInt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;getInt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;createGetSetting&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;toBool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;parseBool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;getBool&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;createGetSetting&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;seq&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;string&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;`&lt;/span&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="p"&gt;`,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;splitOnComma&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;getStringSequence&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The result of the above code is four different procedures called &lt;code&gt;getSetting&lt;/code&gt; which look for a setting in the command line arguments, an environment variable, or a config file, and return it as the expected&amp;nbsp;type.&lt;/p&gt;
&lt;p&gt;Even though the above code is a mess and I&amp;#8217;m probably going to rethink it, I will say this - writing the template was surprisingly&amp;nbsp;intuitive.&lt;/p&gt;
&lt;p&gt;Nim&amp;#8217;s meta-programming features become even more powerful with macros and pragmas, but I haven&amp;#8217;t really gotten into them yet so I can&amp;#8217;t say much about&amp;nbsp;them.&lt;/p&gt;
&lt;h2&gt;Standard&amp;nbsp;Library&lt;/h2&gt;
&lt;p&gt;There&amp;#8217;s some pretty great stuff in the standard library, including very easy to use asynchronous http and networking libraries, and parsers for a variety of text-based file formats. Everything seems to be appropriately cross-platform as well. I haven&amp;#8217;t got much else to say about&amp;nbsp;it!&lt;/p&gt;
&lt;h2&gt;Python&amp;nbsp;Modules&lt;/h2&gt;
&lt;p&gt;Something I&amp;#8217;m always looking out for in a language is the ability to write Python modules in it. There seem to be a &lt;a href="https://github.com/Pebaz/nimporter#nimporter" title="Nimporter on GitHub"&gt;couple&lt;/a&gt; of Nim &lt;a href="https://github.com/sstadick/nython#nython" title="Nython on GitHub"&gt;libraries&lt;/a&gt; for &lt;a href="https://medium.com/statch/speeding-up-python-code-with-nim-ec205a8a5d9c" title="Nimpy package benchmark"&gt;doing this&lt;/a&gt;, both based on an underlying &lt;a href="https://github.com/yglukhov/nimpy" title="Nimpy on GitHub"&gt;nimpy&lt;/a&gt; library. They both look incredibly easy to use, but notably the support for exporting Python classes in nimpy seems to be experimental. It is also a bit unclear how it deals with Python objects as parameters of procedures rather than basic&amp;nbsp;types.&lt;/p&gt;
&lt;p&gt;My only point of comparison is &lt;a href="https://cython.org/" title="Cython project homepage"&gt;Cython&lt;/a&gt;, which is a really cool project that compiles Python code to C, and includes an optional extended syntax for optimisation, which is essentially writing C code but with a Python-like syntax. As cool as this is I think the breadth of options is confusing, and when you get down to writing optimised routines things start to break in very unhelpful C-like way - i.e. successful compiles and unceremonious runtime&amp;nbsp;segfaults.&lt;/p&gt;
&lt;p&gt;I much prefer the idea of writing modules in a language that is its own thing, and with Nim being as easy to write as it is, I&amp;#8217;m excited to try it for this&amp;nbsp;purpose.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;I didn&amp;#8217;t perform even rudimentary benchmarks, but I think it&amp;#8217;s safe to assume that anything written in Nim will be faster than the equivalent Python code. Luz runs instantaneously, and Sparkle responds to requests almost instantaneously as well. Neither of them are doing anything that I wouldn&amp;#8217;t expect Python to do at an acceptable speed under the same circumstances,&amp;nbsp;however.&lt;/p&gt;
&lt;p&gt;One thing about Nim benchmarks that I have seen is that they are generally performed with the &lt;code&gt;-d:danger&lt;/code&gt; compiler flag, which disables all runtime checks. This is done in the name of &amp;#8220;fairness&amp;#8221; in comparison with C, but it doesn&amp;#8217;t really seem fair to me if the norm for the language in production is &lt;code&gt;-d:release&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;I definitely found Nim very natural to develop in. Unlike Rust, which I also tried (&lt;em&gt;failed&lt;/em&gt;) to learn recently, most of the concepts were already familiar to me from other languages, and the syntax was also very familiar. I often found myself writing correct Nim code first time, and where I made mistakes they were flagged during compilation in a way that was easy to understand. Runtime errors are also handled relatively gracefully - no segfaults even though Nim compiles to C, like Cython&amp;nbsp;does.&lt;/p&gt;
&lt;p&gt;Overall, a very interesting language that I look forward to doing more&amp;nbsp;with.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/khoulihan/sparkle" title="Sparkle on GitHub"&gt;&lt;img alt="Sparkle in action" src="https://blog.hyperlinkyourheart.com/images/nimpressions/sparkle_screenshot.png" title="It's called Sparkle because it's barely there..."&gt;&lt;/a&gt;&lt;/p&gt;</content><category term="Programming"></category><category term="nim"></category><category term="python"></category><category term="c-sharp"></category></entry><entry><title>Recent Movie Watchings</title><link href="https://blog.hyperlinkyourheart.com/various-movies.html" rel="alternate"></link><published>2022-05-09T14:00:00+02:00</published><updated>2022-05-09T14:00:00+02:00</updated><author><name>Kevin Houlihan</name></author><id>tag:blog.hyperlinkyourheart.com,2022-05-09:/various-movies.html</id><summary type="html">&lt;p&gt;Movies I watched recently that I have a bit to say about&amp;#8230; but not too&amp;nbsp;much.&lt;/p&gt;</summary><content type="html">&lt;p&gt;I&amp;#8217;ve watched a lot of movies recently that I have a bit to say about, but not enough for a big post dissecting them on their own, like &lt;a href="https://blog.hyperlinkyourheart.com/wrong-turn.html"&gt;Wrong Turn&lt;/a&gt;  and &lt;a href="https://blog.hyperlinkyourheart.com/cybercentrism.html"&gt;Ready Player One&lt;/a&gt;, so I&amp;#8217;m just throwing them all together&amp;nbsp;here.&lt;/p&gt;
&lt;h2&gt;Kimi&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://www.imdb.com/title/tt14128670/?ref_=fn_al_tt_1"&gt;Kimi&lt;/a&gt; is a 2022 psychological thriller about an agoraphobic woman, Angela, who works from home for a smart speaker company - creators of the eponymous &amp;#8220;Kimi&amp;#8221; - listening to supposedly anonymised audio clips that the speaker&amp;#8217;s &lt;span class="caps"&gt;AI&lt;/span&gt; couldn&amp;#8217;t understand. On one of the clips she hears what she believes to be an assault in the background, and when her employers are reluctant to investigate she has to (&lt;em&gt;gulp&lt;/em&gt;)&amp;#8230; leave her&amp;nbsp;apartment!&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.imdb.com/title/tt14128670/?ref_=fn_al_tt_1" title="First movie I've seen set during the pandemic!"&gt;&lt;img alt="Screenshot from Kimi, of Angela out and about and wearing a mask" src="https://blog.hyperlinkyourheart.com/images/various-movies/kimi.jpg" title="First movie I've seen set during the pandemic!"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The main thing that I really liked about this movie was the portrayal of her struggle to leave her apartment, and the paradoxical sense of claustrophobia when she does. I felt much the same at one point in my life and it rang true to&amp;nbsp;me.&lt;/p&gt;
&lt;p&gt;On the other hand, when it gets down to &lt;em&gt;thriller time&lt;/em&gt;, the action is quite repetitive and pointless. She gets captured, escapes, captured again almost straight away, escapes again right outside her building, and then there is somebody waiting for her in her apartment anyway. Boring. It gets better from there, but too&amp;nbsp;late.&lt;/p&gt;
&lt;p&gt;One thing I really didn&amp;#8217;t like was the role of the smart speaker, Kimi. Although the plot early on does highlight a lack of privacy and data protection when Angela is able to find out whose speaker recorded the clips, and obtain further recordings, this is undermined by the plot being fundamentally about solving a murder thanks to the speaker&amp;#8217;s ubiquitous surveillance. It then takes on a heroic role at the climax when Angela is able to outwit several hired goons by ordering it to do various things like cut the lights and play music and so on. Overall, I would say the movie comes down on the side of being pro corporate&amp;nbsp;surveillance.&lt;/p&gt;
&lt;h2&gt;Mary&amp;nbsp;Shelley&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://www.imdb.com/title/tt3906082/?ref_=nv_sr_srsg_2"&gt;This 2017 historical drama&lt;/a&gt; is about the life of Mary Shelley and the sources of inspiration for her novel Frankenstein. Turns out &lt;em&gt;men&lt;/em&gt; are the real&amp;nbsp;monster??&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.imdb.com/title/tt3906082/?ref_=nv_sr_srsg_2" title="[Stares motherfuckerly]"&gt;&lt;img alt="Screenshot from Mary Shelley, of Mary (played by Elle Fanning) in a bonnet" src="https://blog.hyperlinkyourheart.com/images/various-movies/Mary-Shelley.jpg" title="[Stares motherfuckerly]"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I enjoyed this one a lot. I read up about her a bit after watching it and it seems like it was a bit loose with some of the details of her life (like how many children she had, and when they died), but what am I a Mary Shelley&amp;nbsp;scholar?&lt;/p&gt;
&lt;p&gt;Like Frankenstein, it explores the theme of men&amp;#8217;s irresponsibility towards the procreative act, and neglect of their progeny, but more explicitly, and as such it&amp;#8217;s a great complement to the book. Interestingly, the male characters don&amp;#8217;t really seem to get it, and focus on the idea that Frankenstein is about Mary alone feeling neglected, rather that a more general lack of responsibility on their part. She doesn&amp;#8217;t correct&amp;nbsp;them.&lt;/p&gt;
&lt;h2&gt;The Death of&amp;nbsp;Stalin&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://www.imdb.com/title/tt4686844/?ref_=fn_al_tt_1"&gt;The Death of Stalin&lt;/a&gt; is a political black comedy from 2017 about the aftermath of Stalin&amp;#8217;s death. I found it pretty funny, but it was also deeply weird to hear a bunch of undisguised American and British accents from characters in a movie set in the Soviet Union. Probably it would have been worse if they put on stereotypical Russian accents, of course, but Cockney&amp;nbsp;Stalin?&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.imdb.com/title/tt4686844/?ref_=fn_al_tt_1" title="Cockney Stalin?? Ridiculous!"&gt;&lt;img alt="Screenshot from The Death of Stalin, of Stalin laughing right before he has a stroke" src="https://blog.hyperlinkyourheart.com/images/various-movies/Stalin.jpg" title="Cockney Stalin?? Ridiculous!"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;As usual, I would probably prefer to see something from post-soviet creators examining their own history, through a satirical lens or&amp;nbsp;otherwise.&lt;/p&gt;
&lt;h2&gt;The&amp;nbsp;Batman&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://www.imdb.com/title/tt1877830/?ref_=nv_sr_srsg_0"&gt;The Batman&lt;/a&gt; is the latest in the saga of the Bat-men, this time starring Bobby Battinson. I think it might be my new favourite Batman movie, though I didn&amp;#8217;t see the Ben Affleck one so I am not qualified to declare it the &lt;em&gt;objectively best&lt;/em&gt; Batman&amp;nbsp;movie.&lt;/p&gt;
&lt;p&gt;The movie leans heavily into noir and gothic aesthetics, and imagines Bruce Wayne as a moody orphan who is uninterested in much outside of being a bat - including the effect his inherited wealth is having on society. Having become, under his father&amp;#8217;s watch, a sort of slush fund for corruption, Bruce Wayne&amp;#8217;s wealth is the underlying cause of much of the violence that Batman seeks to combat alongside his friends in the&amp;nbsp;police.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.imdb.com/title/tt1877830/?ref_=nv_sr_srsg_0" title="Emomelon Wayne"&gt;&lt;img alt="Screenshot from The Batman, of emo Bruce Wayne" src="https://blog.hyperlinkyourheart.com/images/various-movies/batman.jpeg" title="Emomelon Wayne"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;His main adversary is the Riddler, portrayed here as a vigilante serial killer with shades of Seven&amp;#8217;s John Doe and the Zodiac killer. While Batman is beating up common criminals and thugs, the Riddler targets the powerful and corrupt, and as such it&amp;#8217;s hard to identify the villainy in his actions for much of the movie (aside from the fact that he&amp;#8217;s, y&amp;#8217;know, doing murders and all that). The general public certainly see him as a hero. Meanwhile, he sees himself and Batman as partners, playing off each other in a common crusade to clean up the city (and who else could, but the only two men smart enough to appreciate a good riddle). It isn&amp;#8217;t until his plan to &amp;#8220;wipe the scum off the streets&amp;#8221; by flooding the city is revealed that we see his contempt for the innocent as well as the&amp;nbsp;guilty.&lt;/p&gt;
&lt;p&gt;Unfortunately the overall politics of the movie could probably be summed up as &amp;#8220;we just need more good billionaires&amp;#8221;. Bruce comes to realise that his vast wealth comes with responsibilities, and it seems like he&amp;#8217;s going to do some philanthropy alongside his nightly costumed kickpunching. I guess we&amp;#8217;ll find out in the sequel if enlightened liberal capitalism is the solution to capitalism&amp;#8217;s&amp;nbsp;problems.&lt;/p&gt;
&lt;p&gt;I didn&amp;#8217;t even realise that Colin Farrell was in this until I saw his name in the credits. He&amp;#8217;s completely unrecognisable as the&amp;nbsp;Penguin.&lt;/p&gt;
&lt;h2&gt;Choose or&amp;nbsp;Die&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://www.imdb.com/title/tt11514780/?ref_=fn_al_tt_1"&gt;Choose or Die&lt;/a&gt; is a 2022 horror thriller about a cursed retro video game. This seemed like a fun premise, but unfortunately the movie as a whole was fucking&amp;nbsp;crap.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.imdb.com/title/tt11514780/?ref_=fn_al_tt_1" title="There was a pixel art sequence leading up to this scene, out of nowhere"&gt;&lt;img alt="Screenshot from Choose or Die, of Kayla and Isaac standing in front of Isaac's car, looking concerned" src="https://blog.hyperlinkyourheart.com/images/various-movies/cod.jpg" title="There was a pixel art sequence leading up to this scene, out of nowhere"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;My main fault with it is that the game (named &lt;span class="caps"&gt;CURS&lt;/span&gt;&amp;gt;R) has apparently boundless powers to reshape reality to its whims, and that the choices it presents players with are seemingly arbitrary, and differ wildly in terms of their consequences. For example, the first choice the main character, Kayla, is given is between coffee and cake in a diner, with apparently no negative consequences. Another character&amp;#8217;s first choice is between eating a computer or eating their own arm - both potentially fatal, one would think. For one of the &amp;#8220;levels&amp;#8221; of the game, Kayla is asked to choose between a blue door or a red one, with no other information. It reminded me of the first text-based video game I wrote when I was 7, which was just a collection of random scenarios where every path ultimately ended with the player being eaten by a&amp;nbsp;tiger.&lt;/p&gt;
&lt;p&gt;The climax sees Kayla facing off with a previous player (who we are introduced to in the opening scene, but learn very little about). At this point a moral is shoehorned in about white male entitlement in videogaming - which would be a fine theme if it wasn&amp;#8217;t introduced so late and handled so&amp;nbsp;clumsily.&lt;/p&gt;
&lt;p&gt;I did like the grungy 80&amp;#8217;s aesthetic, and that it seemed almost self-aware about how played out that kind of nostalgia is at this point. Also Asa Butterfield is great as a basement-dwelling retro video gaming obsessive. I do love me some Asa&amp;nbsp;Butterfield&amp;#8230;&lt;/p&gt;
&lt;p&gt;&lt;img alt="Screenshot from Choose or Die, of Isaac (played by Asa Butterfield)" src="https://blog.hyperlinkyourheart.com/images/various-movies/asa.jpg" title="Buttery good"&gt;&lt;/p&gt;</content><category term="Movies"></category><category term="fiction"></category><category term="politics"></category></entry><entry><title>Sim-Universe</title><link href="https://blog.hyperlinkyourheart.com/simulation.html" rel="alternate"></link><published>2022-04-16T15:43:00+02:00</published><updated>2022-04-16T15:43:00+02:00</updated><author><name>Kevin Houlihan</name></author><id>tag:blog.hyperlinkyourheart.com,2022-04-16:/simulation.html</id><summary type="html">&lt;p&gt;Thoughts on the simulation&amp;nbsp;argument.&lt;/p&gt;</summary><content type="html">&lt;p&gt;I just done watched &lt;a href="https://www.youtube.com/channel/UCrr7y8rEXb7_RiVniwvzk9w" title="Thought Slime"&gt;Thought Slime&amp;#8217;s&lt;/a&gt; &lt;a href="https://www.youtube.com/watch?v=erkM0abWBfQ" title="Elon Musk is wrong about simulation theory, how uncharacteristic of him."&gt;video about the simulation argument&lt;/a&gt; (actually many months ago by the time I&amp;#8217;m actually publising this), and it&amp;#8217;s a topic about which I&amp;#8217;ve had some thoughts myself, so I thought maybe it was time to write some of them&amp;nbsp;down.&lt;/p&gt;
&lt;p&gt;Like comrade Slime, I think that it&amp;#8217;s an interesting thought experiment, but a lot of what is said about it is poorly thought through at best. It&amp;#8217;s particularly frustrating when &lt;a href="https://www.simulation-argument.com/simulation.html" title="Simulation Argument"&gt;Nick Bostrom&amp;#8217;s argument&lt;/a&gt; is held up as &amp;#8220;proof&amp;#8221; of the &amp;#8220;certainty&amp;#8221; that we are living in a simulation, alongside arguments and assertions that completely contradict it. The argument itself doesn&amp;#8217;t claim to be proof of any such thing - it presents three possibilities based on premises about which we have almost no&amp;nbsp;information.&lt;/p&gt;
&lt;h2&gt;Why would we&amp;nbsp;simulate?&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;One thing that later generations might do with their super-powerful computers is run detailed simulations of their forebears or of people like their&amp;nbsp;forebears.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This is Nick&amp;#8217;s description of &lt;em&gt;what&lt;/em&gt; futuristic super-computing civilisations would do with their computational power, but he doesn&amp;#8217;t really get into &lt;em&gt;why&lt;/em&gt; they might do this. Into this absence people pour all sorts of ideas. A common one is that we are equivalent to NPCs in a video-game. A related one is that we exist so that the simulators can pop in and out of our minds and ride us around for some reason - historical educational purposes perhaps, or the thrill of slumming it in the&amp;nbsp;stupid-ages.&lt;/p&gt;
&lt;p&gt;These are interesting concepts for science-fiction, but I don&amp;#8217;t find them compelling as claims about the reality of our world. Video-games are indeed able to present more visually convincing realities than in the past, but they don&amp;#8217;t do that by simulating entire physical universes in minute detail. They might run physics simulations for a variety of things in the vicinity of the player - beyond the bare minimum necessary to convince, they are hollow, simplified facades, and anything not relevant to the context of the current gameplay is non-existent. Similarly, what would it add to a player&amp;#8217;s experience to have NPCs living lives outside of that context and having inner&amp;nbsp;lives?&lt;/p&gt;
&lt;p&gt;Nick Bostrum actually gets into some of the mechanisms that could be used to reduce the computational requirements of a&amp;nbsp;simulation:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;If the environment is included in the simulation, this will require additional computing power – how much depends on the scope and granularity of the simulation. Simulating the entire universe down to the quantum level is obviously infeasible&amp;#8230; But in order to get a realistic simulation of human experience, much less is needed – only whatever is required to ensure that the simulated humans, interacting in normal human ways with their simulated environment, don’t notice any&amp;nbsp;irregularities.&lt;/p&gt;
&lt;p&gt;Distant astronomical objects can have highly compressed representations: verisimilitude need extend to the narrow band of properties that we can observe from our planet or solar system spacecraft. On the surface of Earth, macroscopic objects in inhabited areas may need to be continuously simulated, but microscopic phenomena could likely be filled in ad hoc. What you see through an electron microscope needs to look unsuspicious, but you usually have no way of confirming its coherence with unobserved parts of the microscopic&amp;nbsp;world&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The implicit assumption here is that the simulation is being made convincing for the benefit of the simulated minds (i.e. us), which always run at full resolution. Video-games are not run for the entertainment of NPCs however. If simulations are being run for the amusement of posthuman &amp;#8220;players&amp;#8221;, and they are interested in reducing the computational requirements, as Nick assumes, why would they not prune the most computationally expensive component - simulated human minds that are not immediately relevant to the player&amp;#8217;s current experience? Would they even need to simulate fully conscious humans at all to provide convincing NPCs to&amp;nbsp;players?&lt;/p&gt;
&lt;p&gt;Nick does suggest something akin to such pruning in his original&amp;nbsp;argument:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;In addition to ancestor-simulations, one may also consider the possibility of more selective simulations that include only a small group of humans or a single individual. The rest of humanity would then be zombies or “shadow-people” – humans simulated only at a level sufficient for the fully simulated people not to notice anything&amp;nbsp;suspicious.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;However, it is again expressed as if the purpose of the simulation is solely to fool its unwitting inhabitant(s), with no proposed utility for the creators of the&amp;nbsp;simulation.&lt;/p&gt;
&lt;p&gt;I submit to you that if you are experiencing a private and mundane moment right now, and are conscious of it, you are probably not a character simulated on some posthuman equivalent of a&amp;nbsp;PlayStation.&lt;/p&gt;
&lt;p&gt;A more reasonable suggestion, to my mind, is that we would run such simulations in order to study our own civilisation at different stages of development, or to see how civilisations might develop under different circumstances. Would these simulations even require fully conscious simulated participants in order to be useful? Would they need to simulate the full lives of everybody who has ever lived? Or would they drastically reduce the number of minds needing to be simulated by cutting out all the boring parts? Would there really even be anything to be learned from such&amp;nbsp;simulations?&lt;/p&gt;
&lt;p&gt;This lack of clarity about why a posthuman civilisation would run ancestor simulations is at the heart of a lot of my issues with the argument. Without that understanding, we can&amp;#8217;t really say whether such a civilisation would run them or not, or how many, or what their parameters would be. It&amp;#8217;s just sort of assumed that they probably will because it would be a cool thing to be able to do, and some people say they would do it right now if it were possible. But that&amp;#8217;s an easy thing to say when it&amp;#8217;s impossible, and you don&amp;#8217;t have to worry about the ethical concerns or the resources&amp;nbsp;involved.&lt;/p&gt;
&lt;p&gt;Another type of simulation we might run are of universes with different physical laws, but as the quotes above about simplifying the simulations suggest, these would have a different set of priorities, and wouldn&amp;#8217;t really qualify as &amp;#8220;ancestor simulations&amp;#8221;. Whether they would even result in conscious entities would probably depend on the parameters of the simulation - they wouldn&amp;#8217;t be the goal. If we take seriously the suggestion that we live in this kind of simulation, we can&amp;#8217;t even assume that the simulators are anything like us, not even in their remote past, or that the simulating universe resembles ours in any way - so how can we possibly speculate about their motives, or what is computationally possible in their&amp;nbsp;universe?&lt;/p&gt;
&lt;h2&gt;Simulations Within&amp;nbsp;Simulations&lt;/h2&gt;
&lt;p&gt;One of the silliest suggestions that some people seem to take seriously is that the posthuman civilisation in the base reality would run simulations beyond the point where the simulated civilisations would be running their own simulations, with those simulations running further simulations, and so&amp;nbsp;on.&lt;/p&gt;
&lt;p&gt;Nick likens this scenario to running code in a virtual&amp;nbsp;machine:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;It may be possible for simulated civilizations to become posthuman. They may then run their own ancestor-simulations on powerful computers they build in their simulated universe. Such computers would be “virtual machines”, a familiar concept in computer science. (Java script web-applets, for instance, run on a virtual machine – a simulated computer – inside your&amp;nbsp;desktop.)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;His example is terrible, but the basic assertion is correct, a computer can simulate another computer in various ways, with varying levels of overhead. In the best case, code running in the virtual machine runs directly on the host hardware with no translation necessary. Obviously, this doesn&amp;#8217;t add any processing power - software running in the host has to share its resources with the software running in the virtual&amp;nbsp;machine.&lt;/p&gt;
&lt;p&gt;Now, let&amp;#8217;s think through this scenario a little&amp;nbsp;bit.&lt;/p&gt;
&lt;p&gt;Say you are a posthuman civilisation that has converted an entire planet into a giant computer. All the computation you decide to do is running on this computer. For some reason, you decide to run an ancestor simulation of your quite recent past, such that the simulated universe is on the cusp of achieving their own planet-computer. All of the computation of that universe would actually be running on &lt;em&gt;your&lt;/em&gt; computer, alongside all the existing computation of your civilisation, and all the other work required for the simulation, all the fake stars and physics and advanced posthuman minds. Then you let them run their own simulation of their own recent past - now you have to support the load of three civilisations with planet-sized computers on only one actual physical planet-sized computer. And then four, and then five, and on and&amp;nbsp;on.&lt;/p&gt;
&lt;p&gt;A little while ago we were talking about cutting corners to save resources and focus on running our ancestors minds, and now here we are supporting an infinite regress of posthuman computers for no obvious purpose. There wouldn&amp;#8217;t be any shortcuts here - if a computer 10 levels down wants to compute a hash or calculate millions of primes you would actually have to do the work or &lt;em&gt;they would know&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;There are two possible workarounds/objections to this that I can think&amp;nbsp;of:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Simulations could be run slower than the host reality to allow room for it. Would a time-dilated simulation be useful? I guess that depends on what you&amp;#8217;re running it&amp;nbsp;for!&lt;/li&gt;
&lt;li&gt;Posthuman level simulations would only be allowed to develop once the host reality had converted enough matter to &lt;em&gt;pure computer&lt;/em&gt; that supporting them was not a burden. In other words, the simulations would always have to lag behind by some significant&amp;nbsp;amount.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Fair enough, I guess that would do it, if keeping the simulations going is really important, you might always dedicate a proportional amount of your ever increasing computational resources to them. I do come back to the why though - would a simulation of a posthuman-level civilisation be a fun game for posthumans? Would there be anything to learn from it that you didn&amp;#8217;t document when you were going through that&amp;nbsp;phase?&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;One consideration that counts against the multi-level hypothesis is that the computational cost for the basement-level simulators would be very great. Simulating even a single posthuman civilization might be prohibitively expensive. If so, then we should expect our simulation to be terminated when we are about to become&amp;nbsp;posthuman&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Oh, well. Better to return to monke then, lest techno-god smite us for our&amp;nbsp;arrogance.&lt;/p&gt;
&lt;h2&gt;If God Did Not&amp;nbsp;Exist&amp;#8230;&lt;/h2&gt;
&lt;p&gt;One possibility for why a posthuman civilization might choose not to run ancestor simulations is that doing so would raise some thorny ethical concerns. Take it away&amp;nbsp;Nick:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;One can speculate that advanced civilizations all develop along a trajectory that leads to the recognition of an ethical prohibition against running ancestor-simulations because of the suffering that is inflicted on the inhabitants of the&amp;nbsp;simulation&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Yes I think that might be likely&amp;#8230; wait, what are&amp;nbsp;you&amp;#8230;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;However, from our present point of view, it is not clear that creating a human race is&amp;nbsp;immoral&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;em&gt;Ooof&lt;/em&gt;. It&amp;#8217;s not just &lt;em&gt;creating a human race&lt;/em&gt; that we&amp;#8217;re talking about here, it&amp;#8217;s &lt;em&gt;creating a human race and trapping them in a false reality for our own edification or amusement&lt;/em&gt;, and in some hypothetical scenarios, &lt;em&gt;instantly terminating billions of them when they reach a certain level of development&lt;/em&gt;. I think most people today would baulk at the prospect of treating even a single person like that, much less generation after generation of unwitting&amp;nbsp;playthings.&lt;/p&gt;
&lt;p&gt;Even worse are the moral implications for us, today, of taking some of Nick&amp;#8217;s proposals seriously. In relation to the idea that many minds might be simulated only partially some amount of the time in order to save resources (discussed above), he suggests that it would also be a way for the simulators to avoid inflicting&amp;nbsp;suffering:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;There is also the possibility of simulators abridging certain parts of the mental lives of simulated beings and giving them false memories of the sort of experiences that they would typically have had during the omitted interval. If so, one can consider the following (farfetched) solution to the problem of evil: that there is no suffering in the world and all memories of suffering are illusions. Of course, this hypothesis can be seriously entertained only at those times when you are not currently&amp;nbsp;suffering.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;You weren&amp;#8217;t traumatised, you see, you just have a false memory of trauma. And no need to worry about the consequences if you feel compelled to abuse, murder or rape: those are just zombie shadow-people you&amp;#8217;re hurting, and they don&amp;#8217;t really feel pain! Nothing is real and nothing&amp;nbsp;matters!&lt;/p&gt;
&lt;p&gt;But wait! Maybe our simulators will take it upon themselves to reward or punish us for our behaviour in their simulation (without informing us that they will do so, or on what basis), and dedicate ludicrous amounts of resources to simulating all the minds they have ever simulated, indefinitely, in an&amp;nbsp;afterlife:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Further rumination on these themes could climax in a naturalistic theogony that would study the structure of this hierarchy, and the constraints imposed on its inhabitants by the possibility that their actions on their own level may affect the treatment they receive from dwellers of deeper levels. For example, if nobody can be sure that they are at the basement-level, then everybody would have to consider the possibility that their actions will be rewarded or punished, based perhaps on moral criteria, by their simulators. An afterlife would be a real&amp;nbsp;possibility.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;It genuinely disturbs me that there are people who are only good because they believe there is some force outside the universe that will reward them for it, or punish them for misbehaviour - and, even worse, people who would take on the role of cosmic arbiter themselves if given the&amp;nbsp;chance.&lt;/p&gt;
&lt;h2&gt;Postsingular&amp;nbsp;Posthumans&lt;/h2&gt;
&lt;p&gt;Inevitably, discussions about the simulation argument are little more than speculation based on almost no information. The kind of civilisation that would be capable of running such simulations would be one that has passed through a technological singularity - a point at which technological progress becomes so rapid that its path is impossible to predict. In fact the simulation argument requires that a civilisation has achieved the ability to simulate a human-equivalent mind - an Artificial General Intelligence - widely considered to be the invention that will instigate the singularity, since such an intelligence would probably be able to improve itself at an exponential&amp;nbsp;rate.&lt;/p&gt;
&lt;p&gt;We have zero examples of a post-singularity, posthuman civilisation, and only one example of a human-level civilisation, on which to base our speculations. What will super-intelligent posthumans value? Almost by definition such a civilisation would be beyond our&amp;nbsp;comprehension.&lt;/p&gt;
&lt;p&gt;The simulation argument seems mostly, to me, to be an attempt to imagine God in a way that is appealing to 21st century techies. I&amp;#8217;m inclined to think that such a god, like all others, is not just unknowable, but&amp;nbsp;non-existent.&lt;/p&gt;</content><category term="Philosophy"></category><category term="sci-fi"></category><category term="religion"></category></entry><entry><title>Joplin &amp; Syncthing</title><link href="https://blog.hyperlinkyourheart.com/joplin.html" rel="alternate"></link><published>2021-08-28T15:32:00+02:00</published><updated>2021-08-28T15:32:00+02:00</updated><author><name>Kevin Houlihan</name></author><id>tag:blog.hyperlinkyourheart.com,2021-08-28:/joplin.html</id><summary type="html">&lt;p&gt;I recently switched from Evernote to Joplin, using Syncthing to sync my notes between&amp;nbsp;devices.&lt;/p&gt;</summary><content type="html">&lt;p&gt;I&amp;#8217;ve been using Evernote for quite a few years now for keeping work and personal notes, despite never really being happy with it. Aside from the fact of entrusting my data to a private company, most of my issues with it were minor nuisances, and momentum kept me using it because I didn&amp;#8217;t see a good alternative that seemed impressive enough to be worth the hassle of migration. I considered writing my own alternative many times, but of course there were even greater barriers to&amp;nbsp;that!&lt;/p&gt;
&lt;p&gt;A few weeks ago I decided to finally take the plunge and try out &lt;a href="https://joplinapp.org/" title="Joplin's website"&gt;Joplin&lt;/a&gt;, a &lt;span class="caps"&gt;FOSS&lt;/span&gt; note-taking desktop and mobile&amp;nbsp;app.&lt;/p&gt;
&lt;h2&gt;Joplin&lt;/h2&gt;
&lt;p&gt;Migrating to Joplin was relatively easy, as it can import Evernote&amp;#8217;s &amp;#8220;&lt;span class="caps"&gt;ENEX&lt;/span&gt;&amp;#8221; export format. Unfortunately Evernote made me &lt;a href="https://help.evernote.com/hc/en-us/articles/209005557-Export-notes-and-notebooks-as-ENEX-or-HTML" title="How to export notes from Evernote"&gt;jump through a few hoops&lt;/a&gt; to create these files - the web app, which I usually use, wouldn&amp;#8217;t do it, and the export had to be done notebook by&amp;nbsp;notebook.&lt;/p&gt;
&lt;p&gt;Joplin did a fairly good job of converting the formatting to its native Markdown, but my notes were a mess anyway so it hardly mattered. The main thing that seemed to go wrong was headers being converted to bold text instead of actual header lines. I also had to reorganise the notebook hierarchy since the notebooks were exported and imported individually. With that done I was about where I had been with Evernote, albeit only on my main&amp;nbsp;computer.&lt;/p&gt;
&lt;p&gt;Joplin includes both a Markdown editor and a &lt;span class="caps"&gt;WYSIWYG&lt;/span&gt; editor. I haven&amp;#8217;t tried the &lt;span class="caps"&gt;WYSIWYG&lt;/span&gt; editor because I like writing in Markdown these days, and I&amp;#8217;m hoping using it will result in more structured notes than I&amp;#8217;ve been keeping in the past. The default layout has the Markdown and rendered output side by side which I do find a little strange - I can never decide which side I should be reading. However, there is a button in the top right corner of the window to switch between dedicated editing, reading and side-by-side&amp;nbsp;modes.&lt;/p&gt;
&lt;h2&gt;Sync That&amp;nbsp;Thing&lt;/h2&gt;
&lt;p&gt;Joplin can sync between instances using all of the main cloud storage services as well as its own cloud offering. However, the method that appealed to me, because it keeps my notes out of anybody else&amp;#8217;s hands, was to use &lt;a href="https://syncthing.net/" title="Syncthing website"&gt;Syncthing&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Syncthing is a &lt;span class="caps"&gt;P2P&lt;/span&gt; file synchronisation protocol and app that supports all the operating systems that I use (not iOS), and doesn&amp;#8217;t require any complex network&amp;nbsp;configuration.&lt;/p&gt;
&lt;p&gt;Joplin is actually unaware of Syncthing - to use it, you need to select the &amp;#8220;File system&amp;#8221; sync target and point it to a folder. It will periodically export changes to this folder and import any changes it finds&amp;nbsp;there.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Joplin sync settings" src="https://blog.hyperlinkyourheart.com/images/joplin/joplinsettings.png" title="Joplin sync settings"&gt;&lt;/p&gt;
&lt;p&gt;Syncthing is managed via a web interface on localhost port 8384 by default. There are two main tasks to perform here - connecting your devices, and sharing your notes&amp;nbsp;folder.&lt;/p&gt;
&lt;p&gt;Devices in Syncthing are identified by quite unwieldy &lt;span class="caps"&gt;SHA&lt;/span&gt;-256 hashes, but it provides a number of ways to simplify exchanging these. Devices on the &lt;span class="caps"&gt;LAN&lt;/span&gt; are listed in the add device dialog, and if you&amp;#8217;re using it on mobile there is an option to scan a &lt;span class="caps"&gt;QR&lt;/span&gt; code for the device you&amp;#8217;re connecting. Devices have to grant permission to other devices that add them, and once they do you can choose which folders to&amp;nbsp;share.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Add device" src="https://blog.hyperlinkyourheart.com/images/joplin/adddevice.png" title="Add device"&gt;&lt;/p&gt;
&lt;p&gt;Adding a folder is just a matter of entering the path to it on the file system, and giving it a name. You can select existing remote devices to sharing with on the &amp;#8220;Sharing&amp;#8221; tab, or share it later. Devices have to approve shared folders as well, and you will have an opportunity to choose a target location at that point as&amp;nbsp;well.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Add folder" src="https://blog.hyperlinkyourheart.com/images/joplin/addfolder.png" title="Add folder"&gt;&lt;/p&gt;
&lt;p&gt;Syncthing on Android actually has two user interfaces, a native one and the same web &lt;span class="caps"&gt;UI&lt;/span&gt; as is available on desktop, which is a bit confusing. I found I had to drop down to the web &lt;span class="caps"&gt;UI&lt;/span&gt; to approve remote device and folder&amp;nbsp;connections.&lt;/p&gt;
&lt;p&gt;The Joplin configuration should be basically the same on any devices you want to sync - just choose the &amp;#8220;File system&amp;#8221; target and point to the synced notes folder. On desktop there is an option to clear the local notes and take everything fresh from the sync target, but the Android app seems to be missing this. As such, you might end up with multiple copies of Joplin&amp;#8217;s initial documentation&amp;nbsp;notes.&lt;/p&gt;
&lt;h2&gt;Snags&lt;/h2&gt;
&lt;p&gt;There are a few things to watch out for, and a few things that I personally find a bit&amp;nbsp;confusing.&lt;/p&gt;
&lt;p&gt;The first problem I encountered was due to my hesitation about where to have my phone&amp;#8217;s photos stored on my laptop. I accepted the share to one location initially, and when I later deleted and recreated the share in another location I somehow orphaned 33 files. My phone is still stuck at 99% synced as a&amp;nbsp;result.&lt;/p&gt;
&lt;p&gt;One thing that appears strange to me is that you can share a folder from one device to another, and then share it from the second device to a third without the third device being aware of the first. I&amp;#8217;m not sure if there are any consequences to that setup or if i is functionally the same as having all the devices aware of each&amp;nbsp;other.&lt;/p&gt;
&lt;p&gt;On the Joplin side, the formats of the sync repository occasionally need to be updated for new versions of the software. It then becomes unusable by older versions. It remains to be seen how much of an issue this will be - my main worry is that I will update one device beyond what is available on one of the others, or that I will be forced to update at an inconvenient&amp;nbsp;time.&lt;/p&gt;
&lt;p&gt;I have had one newly created note fail to sync to my phone so far, though it went to another device, and notes created subsequently synced to it no problem. This may have been the result of one of the issues described above, but I haven&amp;#8217;t figured it out&amp;nbsp;yet.&lt;/p&gt;
&lt;p&gt;The final thing to be aware of is that Syncthing won&amp;#8217;t try to resolve conflicts between files, instead choosing and renaming a &amp;#8220;loser&amp;#8221; when conflicts occur. I&amp;#8217;m not sure what Joplin will make of the renamed files, but it&amp;#8217;s something to be aware of if you&amp;#8217;re moving between devices and possibly updating the same note before it can be&amp;nbsp;synced.&lt;/p&gt;
&lt;h2&gt;Beyond&amp;nbsp;Notes&lt;/h2&gt;
&lt;p&gt;Syncthing has actually been a revelation for me. As well as my notes I&amp;#8217;ve been using it to sync photos from my phone to my laptop (previously I was relying on Google Photos), and for sending miscellaneous files from my laptop to my phone (previously Google Drive&amp;#8217;s job). I&amp;#8217;ve also been using it to send video files to my phone, something I wasn&amp;#8217;t even bothering with&amp;nbsp;before.&lt;/p&gt;
&lt;p&gt;It feels great to be able to cut Google out of the loop as well as Evernote, and so far it has been working away well in the background without me having to think much about it after the initial&amp;nbsp;setup.&lt;/p&gt;</content><category term="Technology"></category><category term="foss"></category><category term="privacy"></category></entry><entry><title>Out of Road</title><link href="https://blog.hyperlinkyourheart.com/out-of-road.html" rel="alternate"></link><published>2021-08-11T17:10:00+02:00</published><updated>2021-08-11T17:10:00+02:00</updated><author><name>Kevin Houlihan</name></author><id>tag:blog.hyperlinkyourheart.com,2021-08-11:/out-of-road.html</id><summary type="html">&lt;p&gt;Just a fancy car that took a wrong&amp;nbsp;turn.&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;a href="https://portfolio.hyperlinkyourheart.com/out-of-road.html"&gt;&lt;img alt="Out of Road" src="https://blog.hyperlinkyourheart.com/images/out-of-road/Denial02.png" title="Out of Road"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I don&amp;#8217;t remember where I got the inspiration for this one. I guess I&amp;#8217;ve been thinking a lot about climate change recently, what with the great crypto/&lt;span class="caps"&gt;NFT&lt;/span&gt; debates earlier in the year and recent extreme weather events and wildfires. It seems particularly timely given the recent &lt;a href="https://arstechnica.com/science/2021/08/new-ipcc-climate-report-is-the-clearest-guidebook-for-selecting-a-future/" title="IPCC report"&gt;&lt;span class="caps"&gt;IPCC&lt;/span&gt; report&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In terms of technique, I used a 3D render as a base, which is something I&amp;#8217;ve done before, but this time I used somebody else&amp;#8217;s &lt;span class="caps"&gt;CC&lt;/span&gt; licensed model because cars are kinda&amp;nbsp;complex.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://skfb.ly/6W8x8"&gt;&lt;span class="dquo"&gt;&amp;#8220;&lt;/span&gt;Ford Mustang Mach 1&amp;#8221;&lt;/a&gt; by BaldGuyMartin is licensed under &lt;a href="http://creativecommons.org/licenses/by/4.0/"&gt;Creative Commons Attribution&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Timelapse&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=21VjoNfGcDc"&gt;&lt;img alt="Out of Road" src="https://img.youtube.com/vi/21VjoNfGcDc/0.jpg"&gt;&lt;/a&gt;&lt;/p&gt;</content><category term="Art"></category><category term="pixelart"></category><category term="politics"></category><category term="environmentalism"></category></entry><entry><title>Wrong Turn… into Wokeness</title><link href="https://blog.hyperlinkyourheart.com/wrong-turn.html" rel="alternate"></link><published>2021-07-05T19:40:00+02:00</published><updated>2021-07-05T19:40:00+02:00</updated><author><name>Kevin Houlihan</name></author><id>tag:blog.hyperlinkyourheart.com,2021-07-05:/wrong-turn.html</id><summary type="html">&lt;p&gt;Conservatives fail to understand their own propaganda - but I guess it has the intended effect&amp;nbsp;nonetheless.&lt;/p&gt;</summary><content type="html">&lt;blockquote&gt;
&lt;p&gt;Beyond woke, yet the unintended result is the victims are punished for being woke. Skip it. This is not a Wrong Turn&amp;nbsp;movie.&lt;/p&gt;
&lt;p&gt;&lt;i class="fas fa-comment-alt quote-icon"&gt;&lt;/i&gt; &lt;em&gt;David J, May 02, 2021 - 1.5/5 stars on&amp;nbsp;rottentomatoes.com&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;i class="fas fa-exclamation-triangle spoiler-icon"&gt;&lt;/i&gt;&lt;span class="spoiler-text"&gt;Content warning: homophobia, misogyny, racism, spoilers for the movie Wrong Turn&amp;nbsp;(2021)&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;It seemed pretty clear to me after watching &lt;a href="https://www.imdb.com/title/tt9110170/?ref_=ttmi_tt" title="IMDB entry for Wrong Turn (2021)"&gt;Wrong Turn&lt;/a&gt; that it had a conservative message. I haven&amp;#8217;t seen the previous movies in the franchise, but I understand they are based on some unfair stereotypes about Appalachian people, so it seems a fair enough twist even if it doesn&amp;#8217;t resonate with me personally. When I looked at the &lt;a href="https://www.rottentomatoes.com/m/wrong_turn_2021/reviews?type=user&amp;amp;intcmp=rt-scorecard_audience-score-reviews" title="Audience reviews on Rotten Tomatoes"&gt;audience reviews on Rotten Tomatoes&lt;/a&gt;, however, I discovered that a good many people who seemed like they would be on-board with that perspective instead understood it to be &amp;#8220;woke&amp;#8221;&amp;nbsp;propaganda.&lt;/p&gt;
&lt;h2&gt;The&amp;nbsp;Message&lt;/h2&gt;
&lt;p&gt;In Wrong Turn (2021), a young, white, American woman, Jen, throws off the shackles of a guaranteed prominent position in her father&amp;#8217;s construction business to hike the Appalachian trail with her boyfriend and their friends. Despite warnings from the locals, they stray off the well-worn path, and fall victim to a primitivist cult known as The&amp;nbsp;Foundation.&lt;/p&gt;
&lt;p&gt;It&amp;#8217;s clear from the start that Jen is the character that the audience are supposed to relate to. She&amp;#8217;s torn between the path her father has laid out for her and the ideals of her friends, and is never shown to have taken those ideals to heart personally. She is more down-to-earth and capable than the other characters - she is the one that has to change the tyre when they get a flat en-route, for example, and later she is the only one able to think on her feet in high-pressure&amp;nbsp;situations.&lt;/p&gt;
&lt;p&gt;&lt;img alt="A woman changing a tyre is obviously peak woke" src="https://blog.hyperlinkyourheart.com/images/wrong-turn/tyre_change.jpg" title="A woman changing a tyre is obviously peak woke"&gt;&lt;/p&gt;
&lt;p&gt;Her boyfriend, Darius, is an idealistic, politically active black man who expresses socialist and environmentalist ideas. Their friend group are well educated young urbanites, and include a gay couple - pretty much every review of the movie describes them as &amp;#8220;diverse&amp;#8221;. They&amp;#8217;re incredibly cringey to be honest - when an aggressive local accuses them of never having done a day&amp;#8217;s work in their lives they actually start bragging about their educational achievements and white-collar jobs - all except our hero, Jen, who is just a little lost in&amp;nbsp;life.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Wokevengers assemble!" src="https://blog.hyperlinkyourheart.com/images/wrong-turn/wokevengers.jpg" title="Wokevengers assemble!"&gt;&lt;/p&gt;
&lt;p&gt;Not long after setting out they divert from the trail to find a civil-war fort that one of them is interested in. They quickly become lost, and fall victim to various traps before finally encountering, and murdering, a member of the Foundation. Shortly thereafter they are captured and taken to the Foundation&amp;#8217;s&amp;nbsp;camp.&lt;/p&gt;
&lt;p&gt;Finally we learn what the villains of the piece are all about - not inbred hill people as expected, but an egalitarian, primitivist, socialist cult whose leader has an impeccable hipster coiffure. The surviving friends are put on trial, and their every defense is twisted back on them - &lt;em&gt;they&lt;/em&gt; are the intruders! &lt;em&gt;They&lt;/em&gt; rushed to judgement based on appearances! &lt;em&gt;They&lt;/em&gt; murdered someone in cold blood! &lt;em&gt;They&lt;/em&gt; need to respect the Foundation&amp;#8217;s culture! Such&amp;nbsp;hypocrites!&lt;/p&gt;
&lt;p&gt;&lt;img alt="King of the hipsters" src="https://blog.hyperlinkyourheart.com/images/wrong-turn/venable.webp" title="King of the hipsters"&gt;&lt;/p&gt;
&lt;p&gt;Far from being inbred, this cult thrives on the recruitment of wayward travelers who they either brainwash into accepting their ideology, or blind with a hot poker and leave to fumble in a dark cave. Really subtle&amp;nbsp;stuff.&lt;/p&gt;
&lt;p&gt;Jen and Darius are the only survivors of the trial, and only because Jen convinces the group&amp;#8217;s leader, Venable, that they could be useful members of the community. Jen, considering herself to have no relevant skills apparently, is only able to offer herself, as Venable&amp;#8217;s&amp;nbsp;wife.&lt;/p&gt;
&lt;p&gt;As I mentioned earlier, Jen is actually the only member of the friend group that is portrayed as having any degree of competence or skill relevant to life in the real world. The rest of them are useless, out-of-touch, and varying degrees of obnoxious. But in the woke socialist utopia of the Foundation, she is only valued for her&amp;nbsp;body.&lt;/p&gt;
&lt;p&gt;As a result of her relationship with Venable, Jen ends up pregnant. Of course she never considers a&amp;nbsp;termination.&lt;/p&gt;
&lt;p&gt;It&amp;#8217;s true that &amp;#8220;woke&amp;#8221; people are amongst the victims in this movie, but more importantly it is &amp;#8220;wokeness&amp;#8221; that is the monster, leading good All-American girls like Jen off the conservative middle-class path and into a life of bondage, exploitation and sin. She&amp;#8217;s the character we&amp;#8217;re supposed to find relatable - the rest of them deserve their fate because of their embrace of &amp;#8220;woke ideology&amp;#8221;, and their deaths are likely intended to be entertaining for that reason. They are so in tune with the villains that Darius chooses to stay with them instead of taking the opportunity to&amp;nbsp;escape.&lt;/p&gt;
&lt;p&gt;Let me have another go at summarising what I think this movie is trying to&amp;nbsp;express.&lt;/p&gt;
&lt;p&gt;In Wrong Turn (2021), a young, white, American woman is led astray by her &amp;#8220;woke&amp;#8221; boyfriend and her &amp;#8220;woke&amp;#8221; friends. While seeking to dig up civil war history that is best left buried, they encounter the logical end-point of &amp;#8220;woke&amp;#8221; ideology made manifest, and it abuses them horrifically. Jen escapes thanks to the savvy and skills instilled in her by her conservative upbringing, the refusal of her father to abandon his search for her, and the kindness of the misunderstood locals. She returns to her middle-class path through life by working in the family business, and violently rejects further attempts to lead her back to the horrors of&amp;nbsp;&amp;#8220;wokeness&amp;#8221;.&lt;/p&gt;
&lt;p&gt;In short, this is a conservative movie espousing conservative ideals. I disagree with David J, quoted above - the characters are intentionally punished for being&amp;nbsp;&amp;#8220;woke&amp;#8221;.&lt;/p&gt;
&lt;h2&gt;&lt;span class="dquo"&gt;&amp;#8220;&lt;/span&gt;Hollyweird is dying a slow&amp;nbsp;death&amp;#8221;&lt;/h2&gt;
&lt;p&gt;Let&amp;#8217;s take a look at some of the audience reviews on Rotten Tomatoes from people who seem to have missed the&amp;nbsp;point.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Its just more hand fisted political bs with pretty crappy character&amp;nbsp;development.&lt;/p&gt;
&lt;p&gt;&lt;i class="fas fa-comment-alt quote-icon"&gt;&lt;/i&gt; &lt;em&gt;Gage S, Apr 14, 2021 - 1.5/5&amp;nbsp;stars&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I guess you could interpret &amp;#8220;hand fisted political bs&amp;#8221; to be referring to the conservative political messaging. I choose not&amp;nbsp;to.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;span class="dquo"&gt;&amp;#8220;&lt;/span&gt;Woke&amp;#8221; America is destroying our culture Absolutely&amp;nbsp;irredeemable&lt;/p&gt;
&lt;p&gt;&lt;i class="fas fa-comment-alt quote-icon"&gt;&lt;/i&gt; &lt;em&gt;James A, Mar 14, 2021 - 0.5/5&amp;nbsp;stars&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;It might seem like James gets it, if not for the 0.5&amp;nbsp;stars.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I would have given this a four, if it wasn&amp;#8217;t for the &amp;#8220;over the top&amp;#8221; libtardation seen in the movie that Hollyweird so much loves these days. The mixed race couple, the gay couple, the Arab guy, the Asian guy, Black guy wearing &amp;#8220;Black Owned&amp;#8221; T-Shirt, The racist Sheriff, White-Guilt guy gets mad, calling Confederate monument &amp;#8220;Racism&amp;#8221; like a White-Knight. Pretty embarrassing stuff. Original from 2003 was better, this wasn&amp;#8217;t bad. Hollyweird is dying a slow death, stuff like this in movies is&amp;nbsp;asinine&amp;#8230;&lt;/p&gt;
&lt;p&gt;&lt;i class="fas fa-comment-alt quote-icon"&gt;&lt;/i&gt; &lt;em&gt;Davis H, Mar 06, 2021 - 3/5&amp;nbsp;stars&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;It&amp;#8217;s like this guy stopped watching a third of the way through. Also a pretty explicit example of how the mere existence of characters who are not straight or white is unacceptable political content to some&amp;nbsp;people.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Another woke joke bad&amp;nbsp;movie&lt;/p&gt;
&lt;p&gt;&lt;i class="fas fa-comment-alt quote-icon"&gt;&lt;/i&gt; &lt;em&gt;michael b, Mar 07, 2021 - 0.5/5&amp;nbsp;stars&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Joke&amp;#8217;s on you,&amp;nbsp;buddy.&lt;/p&gt;
&lt;p&gt;This next one&amp;#8217;s pretty gross and misogynistic, and you won&amp;#8217;t miss much if you choose to skip&amp;nbsp;it.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Revolves around a queer braindead friend group, which just so happens to have every single race in it. Due to the fact that the whore with the least amount of brain cells becomes a rambo bitch and survives till the end makes me not able to give this more than 4 stars. Giving this 4 stars because you get to see a dumbass friend group suffer. Also in the foundations court room they never mentioned how the foundation drew first blood with the gay dude getting a big log to the face for the last&amp;nbsp;time.&lt;/p&gt;
&lt;p&gt;&lt;i class="fas fa-comment-alt quote-icon"&gt;&lt;/i&gt; &lt;em&gt;the b, Jun 10, 2021 - 2/5&amp;nbsp;stars&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I had to highlight this one because they understood at least part of what the movie was about - watching &amp;#8220;woke&amp;#8221; straw-people being punished. I can&amp;#8217;t give this review more than 4 stars however because of their views on Jen - I guess because she has sex she must be a &amp;#8220;dumb whore&amp;#8221;. 2/5&amp;nbsp;stars.&lt;/p&gt;
&lt;h2&gt;Final&amp;nbsp;Observation&lt;/h2&gt;
&lt;p&gt;What strikes me about these reviews is that they reveal how the movie reinforces a conservative (or more broadly right-wing) worldview regardless of whether the viewer actually understands the messaging. Either the messaging is understood and received as intended, or the mere presence of &lt;span class="caps"&gt;POC&lt;/span&gt; and gay characters reinforces a perception of a liberal Hollywood elite pushing a &amp;#8220;woke&amp;#8221;&amp;nbsp;agenda.&lt;/p&gt;</content><category term="Movies"></category><category term="fiction"></category><category term="politics"></category></entry><entry><title>Gemini Launch!</title><link href="https://blog.hyperlinkyourheart.com/gemini-launch.html" rel="alternate"></link><published>2021-06-26T14:59:00+02:00</published><updated>2021-06-26T14:59:00+02:00</updated><author><name>Kevin Houlihan</name></author><id>tag:blog.hyperlinkyourheart.com,2021-06-26:/gemini-launch.html</id><summary type="html">&lt;p&gt;All about the launch of my Gemini capsule, and how it is generated and&amp;nbsp;hosted.&lt;/p&gt;</summary><content type="html">&lt;p&gt;In the olden-times, before the Web became basically synonymous with the Internet itself in many people&amp;#8217;s minds, there was another, competing hypertext protocol: &lt;a href="https://en.wikipedia.org/wiki/Gopher_(protocol)" title="Gopher entry on Wikipedia"&gt;Gopher&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I say &amp;#8220;was&amp;#8221;, but of course Gopher never really went away - it was kept alive by enthusiasts, and in recent years there has been a resurgence of interest in it as a sort of haven from the ubiquitous surveillance and relentless commercialisation of the&amp;nbsp;Web.&lt;/p&gt;
&lt;p&gt;I&amp;#8217;ve long been interested in Gopher (I even &lt;a href="https://hyperlinkyourheart.itch.io/gophers" title="Gophers on itch.io"&gt;made a game about it&lt;/a&gt;), and have intended to start a phlog for a while without ever going ahead with it. Something about it always just seemed a little bit awkward and off-putting. I was torn between using Gophermap (i.e. menu) files for everything, or using plain text for posts and sacrificing any hypertextuality. I was torn between finding the need to wrap text to be cool and retro, or a hassle that results in an inferior experience for both creating and consuming&amp;nbsp;content.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://gemini.circumlunar.space/docs/faq.gmi" title="Gemini FAQ"&gt;Gemini&lt;/a&gt; is a new protocol which takes inspiration from both Gopher and the Web, and from a certain perspective, improves on&amp;nbsp;both.&lt;/p&gt;
&lt;p&gt;When I heard about Gemini I didn&amp;#8217;t really get it at first. I thought it was just Gopher with &lt;span class="caps"&gt;SSL&lt;/span&gt;, which is nice, but I figured I&amp;#8217;d get set up on Gopher first and then consider a Gemini mirror. A few days ago I saw a screenshot of the Lagrange browser on Mastodon and started to look into it a bit more. When I realised just how many issues of both Gopher and the web it addresses, I was hooked! I spent several days after that setting up &lt;a href="gemini://gemini.hyperlinkyourheart.com/" title="My capsule"&gt;a capsule&lt;/a&gt; (the Gemini equivalent of a&amp;nbsp;&amp;#8220;site&amp;#8221;).&lt;/p&gt;
&lt;p&gt;&lt;a href="gemini://gemini.hyperlinkyourheart.com/" title="My capsule"&gt;&lt;img alt="My Gemlog in Lagrange" src="https://blog.hyperlinkyourheart.com/images/gemini-launch/gemlog.png" title="My Gemlog in Lagrange"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;Static&amp;nbsp;Generation&lt;/h2&gt;
&lt;p&gt;After experimenting with a Gemini server for a bit and creating a few static &lt;code&gt;text/gemini&lt;/code&gt; files, I decided that I wanted to statically generate my gemlog the same way that I do my blog. I expected to have to write something from scratch to do this, but after some experimentation I was able to get the &lt;a href="https://blog.getpelican.com/" title="Pelican static site generator"&gt;Pelican static site generator&lt;/a&gt; (which I also use for my blog) to both read and output &lt;code&gt;.gmi&lt;/code&gt; files. It does take a bit of configuration however, and I had to monkeypatch a couple of methods in&amp;nbsp;Pelican.&lt;/p&gt;
&lt;p&gt;Unfortunately this means that it is only guaranteed to work with the current version of Pelican, 4.6.0, and could break at any time. Nonetheless, the plugin is &lt;a href="https://github.com/khoulihan/pelican-gemini" title="pelican-gemini plugin"&gt;available on GitHub&lt;/a&gt; if you want to try it&amp;nbsp;out.&lt;/p&gt;
&lt;h3&gt;Gemini&amp;nbsp;Reader&lt;/h3&gt;
&lt;p&gt;The first thing required was a custom &amp;#8220;Reader&amp;#8221; that can handle &lt;code&gt;.gmi&lt;/code&gt; files instead of the usual Markdown or reStructuredText files. It&amp;#8217;s simple enough - it just parses the file up to the first blank line as metadata, and the rest of the content is returned unmodified, since we are also going to output the same&amp;nbsp;format.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;GeminiReader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BaseReader&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;enabled&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;

    &lt;span class="n"&gt;file_extensions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;gmi&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;gemini&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;metadata&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
        &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&amp;quot;&lt;/span&gt;
        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;r&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;end_of_meta&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;
            &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;end_of_meta&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;readline&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;current&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;current&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                    &lt;span class="n"&gt;end_of_meta&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;
                    &lt;span class="k"&gt;continue&lt;/span&gt;
                &lt;span class="n"&gt;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="n"&gt;split&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;: &amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="n"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;split&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;()]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;split&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
            &lt;span class="c1"&gt;# After the first blank line, the rest is content.&lt;/span&gt;
            &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="n"&gt;parsed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;metadata&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
            &lt;span class="n"&gt;parsed&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;process_metadata&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;parsed&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3&gt;Handling Internal&amp;nbsp;Links&lt;/h3&gt;
&lt;p&gt;Pelican has a mechanism for linking to content internal to the site where you start the &lt;span class="caps"&gt;URL&lt;/span&gt; as &lt;code&gt;{static}&lt;/code&gt; or &lt;code&gt;{filename}&lt;/code&gt; and it replaces those with the appropriate paths during generation. However, this didn&amp;#8217;t work with the Gemini link syntax - the replacement is based on a regular expression that assumes the placeholder will be found in an attribute of a &lt;span class="caps"&gt;HTML&lt;/span&gt;&amp;nbsp;element.&lt;/p&gt;
&lt;p&gt;I couldn&amp;#8217;t find any setting or hook in the plugin system to alter this regular expression. There is a setting to customise the part that specifies the braces, so you could change the placeholders to &lt;code&gt;¿¿static??&lt;/code&gt; or something if you like, as long as it is still found in &lt;span class="caps"&gt;HTML&lt;/span&gt;. It seemed like my only option was to replace the method where the problem regex pattern is defined, and use something that matches Gemini links&amp;nbsp;instead.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;_get_intrasite_link_regex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;intrasite_link_regex&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;INTRASITE_LINK_REGEX&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;regex&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;(?P&amp;lt;markup&amp;gt;=&amp;gt; )(?P&amp;lt;quote&amp;gt;)(?P&amp;lt;path&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{}&lt;/span&gt;&lt;span class="s2"&gt;(?P&amp;lt;value&amp;gt;[\S]*))&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;intrasite_link_regex&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;compile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;regex&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;You&amp;#8217;ll notice this also has to include a &amp;#8220;quote&amp;#8221; group because that was present in the &lt;span class="caps"&gt;HTML&lt;/span&gt; version and was expected elsewhere - here it will always be an empty&amp;nbsp;string.&lt;/p&gt;
&lt;p&gt;Unfortunately, the problems didn&amp;#8217;t end there. I found that the placeholders were removed, but not replaced with the absolute &lt;span class="caps"&gt;URL&lt;/span&gt; of the capsule. This turned out to be because urllib is used to join the &lt;span class="caps"&gt;URL&lt;/span&gt; components, and it doesn&amp;#8217;t recognise the gemini protocol. To get around this I had to replace another method, and make a call to a wrapper around &lt;code&gt;urllib.urljoin&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;_urljoin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;is_gemini&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;startswith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;gemini://&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;is_gemini&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;base&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;gemini://&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;https://&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;urljoin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;is_gemini&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;https://&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;gemini://&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3&gt;Gemini&amp;nbsp;Output&lt;/h3&gt;
&lt;p&gt;Pelican uses Jinja2 for its templating, which is happy to work with any type of text file, so creating &lt;code&gt;.gmi&lt;/code&gt; templates wasn&amp;#8217;t an issue. Handily, there is a setting to look for templates with extensions other than &lt;code&gt;.html&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;THEME&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;themes/hypergem&amp;#39;&lt;/span&gt;
&lt;span class="n"&gt;TEMPLATE_EXTENSIONS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;.gmi&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;.gemini&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;To get Pelican to output files with a &lt;code&gt;.gmi&lt;/code&gt; extension instead of &lt;code&gt;.html&lt;/code&gt;, there are a bunch of settings for the different parts of the site. A single &amp;#8220;extension&amp;#8221; setting like for the templates would be nice, but whatchagonnado? I took the opportunity to customise the article location and file names as&amp;nbsp;well.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# These settings are required to output files as .gmi instead of .html&lt;/span&gt;
&lt;span class="n"&gt;ARTICLE_URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;articles/{date:%Y}-{date:%m}-{date:&lt;/span&gt;&lt;span class="si"&gt;%d&lt;/span&gt;&lt;span class="s1"&gt;}-&lt;/span&gt;&lt;span class="si"&gt;{slug}&lt;/span&gt;&lt;span class="s1"&gt;.gmi&amp;#39;&lt;/span&gt;
&lt;span class="n"&gt;ARTICLE_SAVE_AS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ARTICLE_URL&lt;/span&gt;

&lt;span class="n"&gt;DRAFT_URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;drafts/&lt;/span&gt;&lt;span class="si"&gt;{slug}&lt;/span&gt;&lt;span class="s1"&gt;.gmi&amp;#39;&lt;/span&gt;
&lt;span class="n"&gt;DRAFT_SAVE_AS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;DRAFT_URL&lt;/span&gt;

&lt;span class="n"&gt;PAGE_URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;pages/&lt;/span&gt;&lt;span class="si"&gt;{slug}&lt;/span&gt;&lt;span class="s1"&gt;.gmi&amp;#39;&lt;/span&gt;
&lt;span class="n"&gt;PAGE_SAVE_AS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PAGE_URL&lt;/span&gt;

&lt;span class="n"&gt;DRAFT_PAGE_URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;drafts/pages/&lt;/span&gt;&lt;span class="si"&gt;{slug}&lt;/span&gt;&lt;span class="s1"&gt;.gmi&amp;#39;&lt;/span&gt;
&lt;span class="n"&gt;DRAFT_PAGE_SAVE_AS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;DRAFT_PAGE_URL&lt;/span&gt;

&lt;span class="n"&gt;AUTHOR_URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;author/&lt;/span&gt;&lt;span class="si"&gt;{slug}&lt;/span&gt;&lt;span class="s1"&gt;.gmi&amp;#39;&lt;/span&gt;
&lt;span class="n"&gt;AUTHOR_SAVE_AS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;AUTHOR_URL&lt;/span&gt;

&lt;span class="n"&gt;CATEGORY_URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;category/&lt;/span&gt;&lt;span class="si"&gt;{slug}&lt;/span&gt;&lt;span class="s1"&gt;.gmi&amp;#39;&lt;/span&gt;
&lt;span class="n"&gt;CATEGORY_SAVE_AS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;CATEGORY_URL&lt;/span&gt;

&lt;span class="n"&gt;TAG_URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;tag/&lt;/span&gt;&lt;span class="si"&gt;{slug}&lt;/span&gt;&lt;span class="s1"&gt;.gmi&amp;#39;&lt;/span&gt;
&lt;span class="n"&gt;TAG_SAVE_AS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;TAG_URL&lt;/span&gt;

&lt;span class="n"&gt;ARCHIVES_SAVE_AS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;archives.gmi&amp;#39;&lt;/span&gt;
&lt;span class="n"&gt;AUTHORS_SAVE_AS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;authors.gmi&amp;#39;&lt;/span&gt;
&lt;span class="n"&gt;CATEGORIES_SAVE_AS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;categories.gmi&amp;#39;&lt;/span&gt;
&lt;span class="n"&gt;TAGS_SAVE_AS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;tags.gmi&amp;#39;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3&gt;Theme&lt;/h3&gt;
&lt;p&gt;I haven&amp;#8217;t got much to say about this. I wanted the article links to be a bit more descriptive than just the date and title, so I did something similar to what medusae.space does and included the article summary, the category, and the&amp;nbsp;tags.&lt;/p&gt;
&lt;p&gt;It&amp;#8217;s close to general purpose but not quite - I added a custom &lt;code&gt;SITELOGO&lt;/code&gt; setting that is used on the index page with an &lt;span class="caps"&gt;ASCII&lt;/span&gt; art version of my logo generated using &lt;a href="https://ascii-generator.site/" title="ASCII Generator"&gt;ascii-generator.site&lt;/a&gt;, and there is also a custom template for the custom landing page. The index is renamed using a setting, and another page is renamed to index.gmi to take its place. This is so if I want to add content that isn&amp;#8217;t generated by Pelican, I have the scope to do&amp;nbsp;so.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;INDEX_SAVE_AS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;gemlog.gmi&amp;#39;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;Title&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Hyperlink&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Your&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Heart&lt;/span&gt;
&lt;span class="n"&gt;Date&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2021&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;06&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;23&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;22&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;59&lt;/span&gt;
&lt;span class="n"&gt;Slug&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;
&lt;span class="n"&gt;Authors&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Kevin&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Houlihan&lt;/span&gt;
&lt;span class="n"&gt;Summary&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Capsule&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;
&lt;span class="n"&gt;URL&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;gmi&lt;/span&gt;
&lt;span class="n"&gt;save_as&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;gmi&lt;/span&gt;
&lt;span class="n"&gt;Template&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;capsule_intro&lt;/span&gt;
&lt;span class="n"&gt;Status&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;hidden&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Hosting&lt;/h2&gt;
&lt;p&gt;I&amp;#8217;m serving the capsule using &lt;a href="https://github.com/michael-lazar/jetforce" title="Jetforce github"&gt;Jetforce&lt;/a&gt; from a first generation Raspberry Pi which I had lying around and haven&amp;#8217;t done anything with in a while. There was nothing really involved in setting it up beyond what is described in the documentation, except that I installed it in a&amp;nbsp;virtualenv.&lt;/p&gt;
&lt;p&gt;I also took steps to make sure it is running as a dedicated user with no permissions to anything else on the&amp;nbsp;system.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Real professional operation" src="https://blog.hyperlinkyourheart.com/images/gemini-launch/hosting.jpg" title="Real professional operation"&gt;&lt;/p&gt;
&lt;h2&gt;Future&lt;/h2&gt;
&lt;p&gt;I&amp;#8217;m not sure what&amp;#8217;s next, but I&amp;#8217;m excited! I might discuss with the Pelican crew if there are any ways around the issues I encountered that I might have overlooked, or if it could be adapted to be more suited to non-&lt;span class="caps"&gt;HTML&lt;/span&gt; output. If not, maybe a Gemini fork is in order. I have no idea if there are further issues with it beyond the functionality that I&amp;#8217;ve&amp;nbsp;used.&lt;/p&gt;
&lt;p&gt;I have quite a few posts to port over from my blog yet, and I need to get some image optimisation happening there like I have here. Besides that, I guess all I have to do is get to know the&amp;nbsp;community!&lt;/p&gt;
&lt;h2&gt;Visit My Capsule (and&amp;nbsp;Beyond)&lt;/h2&gt;
&lt;p&gt;If you&amp;#8217;re already familiar with Gemini please &lt;a href="gemini://gemini.hyperlinkyourheart.com/" title="My capsule"&gt;check out my capsule&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If you&amp;#8217;re not, well, I still encourage you to visit, but I should probably give you some guidance on getting&amp;nbsp;started.&lt;/p&gt;
&lt;p&gt;If you just want to dip your toes you can browse the Gemini network using a &lt;span class="caps"&gt;HTTP&lt;/span&gt; proxy (&lt;a href="https://proxy.vulpes.one/gemini/gemini.hyperlinkyourheart.com" title="Vulpes.one proxy"&gt;here&amp;#8217;s one&lt;/a&gt;, and &lt;a href="https://portal.mozz.us/gemini/gemini.hyperlinkyourheart.com" title="Mozz.us proxy"&gt;another&lt;/a&gt;). For what I would consider the &amp;#8220;full experience&amp;#8221; you will need a dedicated browser. I&amp;#8217;ve been using &lt;a href="https://github.com/skyjake/lagrange" title="Lagrange browser GitHub"&gt;Lagrange&lt;/a&gt;, and highly recommend it, but there are a &lt;a href="https://gemini.circumlunar.space/software/" title="Gemini software list"&gt;whole bunch of others&lt;/a&gt; if that doesn&amp;#8217;t suit you. Many of them also support Gopher, which makes browsing both into a seamless experience outside of the modern&amp;nbsp;Web.&lt;/p&gt;
&lt;p&gt;When you want to move beyond my capsule, here are some others I&amp;nbsp;recommend:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Medusae.space, a content directory: &lt;a href="gemini://medusae.space" title="Medusae.space content directory"&gt;gemini://medusae.space&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Station, a social network with user accounts: &lt;a href="gemini://station.martinrue.com/" title="Station - where capsuleers hang out"&gt;gemini://station.martinrue.com/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Geddit, an anonymous link aggregator: &lt;a href="gemini://geddit.glv.one/" title="Geddit?"&gt;gemini://geddit.glv.one/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Astrobotany, a community garden game: &lt;a href="gemini://astrobotany.mozz.us/" title="Astrobotany"&gt;gemini://astrobotany.mozz.us/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;A gemlog post about the &amp;#8220;smolnet&amp;#8221;, which explains the appeal far better than I ever could: &lt;a href="gemini://republic.circumlunar.space/users/maugre/200701-smolnet.gmi" title="The smolnet of smol things"&gt;gemini://republic.circumlunar.space/users/maugre/200701-smolnet.gmi&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I leave you with my anxious young poppy, Jennifer, on&amp;nbsp;AstroBotany:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;            O
            |
           \o
            |o
           \/
.  , &lt;span class="ge"&gt;_ . ., l, _&lt;/span&gt; ., _ .  
^      &amp;#39;        `    &amp;#39;

name  : &amp;quot;Jennifer&amp;quot;
stage : anxious young poppy
age   : 2 days
rate  : 1st generation (x1.0)
score : 326788
water : |██████████| 100%
bonus : |          | 2%
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</content><category term="Gemini"></category><category term="python"></category><category term="pelican"></category><category term="blogging"></category><category term="smolnet"></category></entry><entry><title>iRehabilitation</title><link href="https://blog.hyperlinkyourheart.com/mac-rehab.html" rel="alternate"></link><published>2021-06-21T23:55:00+02:00</published><updated>2021-06-21T23:55:00+02:00</updated><author><name>Kevin Houlihan</name></author><id>tag:blog.hyperlinkyourheart.com,2021-06-21:/mac-rehab.html</id><summary type="html">&lt;p&gt;Attempting to keep an old-ish MacBook useful and interesting with&amp;nbsp;Xubuntu.&lt;/p&gt;</summary><content type="html">&lt;p&gt;I&amp;#8217;ve been plagued by temptation lately to buy a &lt;a href="https://www.pine64.org/pinebook-pro/" title="Pinebook Pro"&gt;Pinebook Pro&lt;/a&gt;. My current laptop is really a desktop replacement, a beast that can hardly last an hour untethered from a power socket. It&amp;#8217;s usually not worth the hassle of extracting it from its tangled nest of cables when I want to compute elsewhere, and that&amp;#8217;s fine - it&amp;#8217;s the workhorse. But as a result, the idea of a light, efficient laptop is alluring, especially when it&amp;#8217;s one that runs&amp;nbsp;Linux.&lt;/p&gt;
&lt;h2&gt;Out with the&amp;nbsp;New&lt;/h2&gt;
&lt;p&gt;However, I don&amp;#8217;t really like to buy new devices without good reason. I already have another laptop that meets the criteria of being relatively light and portable - the MacBook Pro that served as my main work machine between 2015 and 2019 when my wife and I were floating around Ireland, France and Spain and living out of the back of our car. I have been using it as a more portable option already on occasion, but it has a few annoying&amp;nbsp;problems:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The battery isn&amp;#8217;t in great shape, so while it&amp;#8217;s a lot better than my main laptop, it&amp;#8217;s nothing like that expected of the Pinebook&amp;nbsp;Pro.&lt;/li&gt;
&lt;li&gt;The &lt;span class="caps"&gt;OS&lt;/span&gt; is outdated. It&amp;#8217;s demanding constantly that I update to a newer version of MacOS, but I don&amp;#8217;t want to. Apparently it could run the latest version, but I don&amp;#8217;t trust Apple to preserve the usability of old&amp;nbsp;devices.&lt;/li&gt;
&lt;li&gt;It runs MacOS. MacOS is fine - it&amp;#8217;s a Unix, it&amp;#8217;s not Windows&amp;#8230; but it still has a lot of little annoyances, it&amp;#8217;s proprietary, and to be honest, I&amp;#8217;m bored with&amp;nbsp;it.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Basically it&amp;#8217;s lost its shine, and isn&amp;#8217;t fun to use&amp;nbsp;anymore.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Happier times house-sitting in Belfast" src="https://blog.hyperlinkyourheart.com/images/mac-rehab/early-days.jpg" title="Happier times house-sitting in Belfast"&gt;&lt;/p&gt;
&lt;h2&gt;A Cunning&amp;nbsp;Plan&lt;/h2&gt;
&lt;p&gt;One of Linux&amp;#8217;s oft-heralded killer use-cases is in giving old hardware new life. I&amp;#8217;ve never really used it for that explicit purpose - whenever I use Linux, it&amp;#8217;s just because I prefer to use Linux, even if it happens to be on old or low-powered machines. This one isn&amp;#8217;t exactly an ancient artifact, but I thought maybe installing a Linux distro with a lightweight desktop environment would help stretch the battery life and make it feel a bit snappier, more like a new&amp;nbsp;machine.&lt;/p&gt;
&lt;p&gt;The two distros I considered were &lt;a href="https://elementary.io/" title="Elementary OS"&gt;ElementaryOS&lt;/a&gt; and &lt;a href="https://xubuntu.org/" title="Xubuntu"&gt;Xubuntu&lt;/a&gt;. I&amp;#8217;m not sure how lightweight Elementary&amp;#8217;s &lt;span class="caps"&gt;DE&lt;/span&gt; is, but I liked the look of it, so I decided to try it out with an eye to maybe using it as my main &lt;span class="caps"&gt;OS&lt;/span&gt; some&amp;nbsp;day.&lt;/p&gt;
&lt;p&gt;First impressions were great - it only used &lt;span class="caps"&gt;700MB&lt;/span&gt; of &lt;span class="caps"&gt;RAM&lt;/span&gt; after booting (compared to nearly &lt;span class="caps"&gt;4GB&lt;/span&gt; for MacOS!), and the degree of visual flair and polish were incredibly impressive. Unfortunately a couple of things put me off - when I cut the &lt;span class="caps"&gt;CPU&lt;/span&gt; frequency to 800MHz I began to experience occasional lag, and at one point the shell crashed with no way to recover&amp;nbsp;it!&lt;/p&gt;
&lt;p&gt;I didn&amp;#8217;t have any experiences like that with Xubuntu. I ran it for a whole day from a &lt;span class="caps"&gt;USB&lt;/span&gt; stick, installed a bunch of software, worked on a blog post, and had no issues - so that was that decision made! It&amp;#8217;s definitely not as visually impressive as Elementary, but I&amp;#8217;d rather a responsive system than a pretty one in this&amp;nbsp;case.&lt;/p&gt;
&lt;h2&gt;Installation&lt;/h2&gt;
&lt;p&gt;Installation was pretty smooth, especially compared to installing Linux on PowerPC Macs back in the day. The only snag was with the proprietary wireless driver. This was easily enough installed using the &amp;#8220;Additional Drivers&amp;#8221; settings dialog when running from the &lt;span class="caps"&gt;USB&lt;/span&gt; stick, but after installation the required driver was no longer available. Having no other means to connect to a network, this was a serious&amp;nbsp;problem!&lt;/p&gt;
&lt;p&gt;Solving this involved a couple of steps. First I enabled the &amp;#8220;&lt;span class="caps"&gt;CDROM&lt;/span&gt;&amp;#8221; source in the Software &lt;span class="amp"&gt;&amp;amp;&lt;/span&gt; Updates settings, under the &amp;#8220;Other Software&amp;#8221; tab. This caused the driver to become available in the Additional Drivers dialog, but it wouldn&amp;#8217;t install. The problem was that the live &lt;span class="caps"&gt;USB&lt;/span&gt; stick was mounted somewhere under &lt;code&gt;/media/kevin&lt;/code&gt;, but apt expected it to be mounted at &lt;code&gt;/media/cdrom&lt;/code&gt;, which didn&amp;#8217;t even exist. Unmounting the &lt;span class="caps"&gt;USB&lt;/span&gt; stick and running the following commands sorted it out, and allowed me to connect to the WiFi to install and upgrade other&amp;nbsp;packages.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;sudo mkdir /media/cdrom
sudo mount /dev/sdb1 /media/cdrom
sudo apt install bcmwl-kernel-source
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Results&lt;/h2&gt;
&lt;p&gt;Unfortunately the results were not quite what I&amp;#8217;d hoped. I performed a test where I played a movie and music on a loop under both OSes, and Xubuntu was down to 10% battery in 1 hour 46 minutes, while Mac &lt;span class="caps"&gt;OS&lt;/span&gt; took 2 hours and 28 minutes to reach the same level. This was with all cores throttled to 800MHz under Xubuntu, and MacOS doing whatever it does naturally to save energy, but full screen and keyboard backlight brightness on&amp;nbsp;both.&lt;/p&gt;
&lt;p&gt;While Xubuntu runs great, and is much more pleasant to use for me, it doesn&amp;#8217;t seem to achieve the same battery life under similar loads. I&amp;#8217;m torn now between the user experience I prefer under Xubuntu and the superior energy efficiency of MacOS&amp;#8230; Or perhaps buying a Pinebook after&amp;nbsp;all!&lt;/p&gt;
&lt;p&gt;&lt;img alt="Cheers on a game-jam well done" src="https://blog.hyperlinkyourheart.com/images/mac-rehab/game-jam.jpg" title="Cheers on a game-jam well done"&gt;&lt;/p&gt;</content><category term="Technology"></category><category term="mac"></category><category term="linux"></category><category term="blogging"></category></entry><entry><title>Recent Art &amp; Portfolio</title><link href="https://blog.hyperlinkyourheart.com/recent-art.html" rel="alternate"></link><published>2021-06-08T00:20:00+02:00</published><updated>2021-06-08T00:20:00+02:00</updated><author><name>Kevin Houlihan</name></author><id>tag:blog.hyperlinkyourheart.com,2021-06-08:/recent-art.html</id><summary type="html">&lt;p&gt;Belated art updates from the last 7&amp;nbsp;months.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Once again I have become neglectful of updating this blog with my artwork, so let&amp;#8217;s do a roundup of the last uh&amp;#8230; &lt;em&gt;7 months?!?!&lt;/em&gt; and maybe I&amp;#8217;ll try to get it back on track from here on. Though I do have a nice &lt;a href="https://portfolio.hyperlinkyourheart.com" title="Portfolio"&gt;portfolio site&lt;/a&gt; now that I have been keeping up to date, so if you really like my art you could be following that as well. More on that&amp;nbsp;below.&lt;/p&gt;
&lt;h2&gt;Socialist Revolutionaries Past &lt;span class="amp"&gt;&amp;amp;&lt;/span&gt;&amp;nbsp;Future&lt;/h2&gt;
&lt;p&gt;Last October I was trying to get back into the development of my game &lt;a href="https://gamejolt.com/games/just-a-robot/185852" title="Just a Robot"&gt;Just a Robot&lt;/a&gt;, and started as I always do by completely re-imagining its entire look. In this case I was inspired by the look of Soviet propaganda and Anarchist&amp;nbsp;woodcuts.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://portfolio.hyperlinkyourheart.com/rise.html"&gt;&lt;img alt="Robot Propaganda" src="https://blog.hyperlinkyourheart.com/images/recent-art/RobotPropaganda03_x1.png" title="Rise"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I followed that up in December with this tribute to the luxurious moustache of Irish revolutionary socialist James&amp;nbsp;Connolly.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://portfolio.hyperlinkyourheart.com/james-connolly.html"&gt;&lt;img alt="James Connolly" src="https://blog.hyperlinkyourheart.com/images/recent-art/JamesConnolly_x1.png" title="The Irish people will only be free, when they own everything from the plough to the stars"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;Spaaaace&lt;/h2&gt;
&lt;p&gt;For the New Year I committed myself to working on more space and sci-fi themed stuff. I started with this depiction of a space station roughly based on the &lt;span class="caps"&gt;ISS&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://portfolio.hyperlinkyourheart.com/space-station.html"&gt;&lt;img alt="Space station" src="https://blog.hyperlinkyourheart.com/images/recent-art/SpaceStation_x1.png" title="Space Station"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Also in January, a spaceship approaching Mercury, with some tricky&amp;nbsp;perspective.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://portfolio.hyperlinkyourheart.com/mercury-approach.html"&gt;&lt;img alt="Mercury approach" src="https://blog.hyperlinkyourheart.com/images/recent-art/MercuryApproach_169_x1.png" title="Getting warm"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;In February I did another piece loosely inspired by Soviet propaganda, and a theme that seemed to be somewhat controversial - the idea of billionaires fleeing into space and leaving the rest of us to our fates. Some people took that as a literal prediction of future events, but I think of it in more allegorical terms - capital is ruining the natural and social environment without any sense of responsibility to the rest of us, while its masters can escape the consequences of their actions without &lt;em&gt;literally leaving the planet&lt;/em&gt; - depicting them doing so is just a good way of describing the situation, in my&amp;nbsp;opinion.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://portfolio.hyperlinkyourheart.com/flight.html"&gt;&lt;img alt="Flight of the Billionaires" src="https://blog.hyperlinkyourheart.com/images/recent-art/Flight02_x1.png" title="Pretty rockets though..."&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;In March I brought it back to Earth and explored the same theme from another perspective, more or&amp;nbsp;less.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://portfolio.hyperlinkyourheart.com/left-behind.html"&gt;&lt;img alt="Left Behind" src="https://blog.hyperlinkyourheart.com/images/recent-art/LeftBehind_x1.png" title="So long..."&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I started a piece in April for Cosmonautics day, but I didn&amp;#8217;t get it finished until June - the capsule and final stage of Vostok 1 in orbit. I did a &lt;a href="https://blog.hyperlinkyourheart.com/out-of-gas-post-mortem.html" title="Out of Gas"&gt;game jam&lt;/a&gt; and an &lt;a href="https://twitter.com/http_your_heart/status/1393938661251690500" title="Diamond hill"&gt;oil painting&lt;/a&gt; in the meantime&amp;nbsp;though!&lt;/p&gt;
&lt;p&gt;&lt;a href="https://portfolio.hyperlinkyourheart.com/vostok1.html"&gt;&lt;img alt="Vostok 1" src="https://blog.hyperlinkyourheart.com/images/recent-art/Vostok1_Final_x1.png" title="Vostok 1"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;And that pretty much brings us up to&amp;nbsp;date!&lt;/p&gt;
&lt;h2&gt;Portfolio&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://portfolio.hyperlinkyourheart.com" title="Portfolio"&gt;My portfolio site&lt;/a&gt;, which went live in July 2020, is another statically-generated site based on &lt;a href="https://blog.getpelican.com/" title="Pelican"&gt;Pelican&lt;/a&gt;, but focused on image galleries instead of blog entries (using the standard gallery plugin). I wanted a central place to put my art that wasn&amp;#8217;t a social media platform, and that would display it optimally. I&amp;#8217;ve linked to it a few times from here despite never actually mentioning&amp;nbsp;it.&lt;/p&gt;
&lt;p&gt;It is inspired by the ideas of Matej Jan on &lt;a href="https://medium.com/retronator-magazine/art-and-the-internet-a3281ba60a88" title="Art and the Internet"&gt;displaying art on the internet&lt;/a&gt;, and attempts to display my pieces at the best integer scaling to fit in the browser window, on a background with appropriate contrast, and with no&amp;nbsp;distractions.&lt;/p&gt;
&lt;p&gt;Of course, I also wanted to keep it as small and responsive as possible, so it does this with about 3kB of javascript, 11kB of &lt;span class="caps"&gt;CSS&lt;/span&gt;, and a selection of minimal &lt;span class="caps"&gt;SVG&lt;/span&gt; backgrounds. Because all the art is pixel art, transferring it at 1x resolution and resizing it in the browser (as discussed in &lt;a href="https://blog.hyperlinkyourheart.com/energy-usage-update.html" title="Energy Usage Update"&gt;recent&lt;/a&gt; &lt;a href="https://blog.hyperlinkyourheart.com/image-optimisation.html" title="Image Optimisation"&gt;posts&lt;/a&gt;) keeps things extremely compact, with the entire portfolio currently only amounting to 390kB. The portfolio does a lot better job of displaying the art than this blog does though, here the images are just resized to max width without attempting an integer&amp;nbsp;scaling.&lt;/p&gt;
&lt;p&gt;I think it looks real nice and that my art looks real nice on it, so &lt;a href="https://portfolio.hyperlinkyourheart.com" title="Portfolio"&gt;go check it&amp;nbsp;out!&lt;/a&gt;&lt;/p&gt;</content><category term="Art"></category><category term="pixelart"></category></entry><entry><title>Image Optimisation</title><link href="https://blog.hyperlinkyourheart.com/image-optimisation.html" rel="alternate"></link><published>2021-05-30T00:27:00+02:00</published><updated>2021-06-01T13:20:00+02:00</updated><author><name>Kevin Houlihan</name></author><id>tag:blog.hyperlinkyourheart.com,2021-05-30:/image-optimisation.html</id><summary type="html">&lt;p&gt;Image optimisation experiments and a helpful Pelican&amp;nbsp;plugin&lt;/p&gt;</summary><content type="html">&lt;p&gt;In the &lt;a href="https://blog.hyperlinkyourheart.com/energy-usage-update.html"&gt;last instalment&lt;/a&gt; of my epic blogging saga I recounted my discovery that the index page of this site had grown to over 1.&lt;span class="caps"&gt;7MB&lt;/span&gt; of content when loaded fresh, largely due to the images. One of my goals for this site was that it be lean - fast to load and energy efficient - and it was not meeting that goal at all. Clearly avoiding Javascript and &lt;span class="caps"&gt;CSS&lt;/span&gt; frameworks was not&amp;nbsp;enough!&lt;/p&gt;
&lt;p&gt;I immediately started thinking about how to improve the situation. Though I had previously ruled out the approach used by &lt;a href="https://solar.lowtechmagazine.com/about.html" title="Low-tech magazine solar powered web site"&gt;Low-tech magazine&amp;#8217;s solar-powered website&lt;/a&gt; because I didn&amp;#8217;t think it would fit my aesthetic, I decided to see if dithering coloured, rather than monochrome, PNGs would work better for me, and improve on the size and quality of an appropriately-sized 75% quality &lt;span class="caps"&gt;JPEG&lt;/span&gt;.&lt;/p&gt;
&lt;h2&gt;&lt;span class="caps"&gt;PNG&lt;/span&gt;&amp;nbsp;Thunderdome&lt;/h2&gt;
&lt;p&gt;The one to beat - baseline 75% &lt;span class="caps"&gt;JPEG&lt;/span&gt;, Size:&amp;nbsp;16.1kb&lt;/p&gt;
&lt;p&gt;&lt;img alt="melanie-pointing_alpha_baseline.jpg" class="poi-no-optimise" src="https://blog.hyperlinkyourheart.com/images/image-optimisation/melanie-pointing_alpha_baseline.jpg" title="melanie-pointing_alpha_baseline.jpg: 16.1kb"&gt;&lt;/p&gt;
&lt;p&gt;Using &lt;a href="https://python-pillow.org/" title="Pillow"&gt;Pillow&lt;/a&gt; and the same &lt;a href="https://github.com/hbldh/hitherdither" title="hitherdither library"&gt;hitherdither&lt;/a&gt; library that Low-tech magazine used for their site, I iterated on a script that output hundreds of compressed variations of a given image using different dithering algorithms and&amp;nbsp;parameters.&lt;/p&gt;
&lt;p&gt;The best results I found were with the Bayesian algorithm with a 32 colour palette, a 2x2 matrix, and an image size half the expected display size. This produced a result that was relatively readable, and reminiscent of pixel art. The size savings varied by image - sometimes up to 10kB, but often only 2-3kB as for this example. These savings are modest compared to the loss in detail, and I think this approach could only be considered because of the unique aesthetic it&amp;nbsp;produces.&lt;/p&gt;
&lt;p&gt;Palette: 32, Dither: bayer, Threshold: 256/8-256-256/8, Order: 2, Size:&amp;nbsp;13.9kb&lt;/p&gt;
&lt;p&gt;&lt;img alt="melanie-pointing_alpha_halved_pal32_dithbayer_order2_thresh8-1-8.png" class="poi-no-optimise" src="https://blog.hyperlinkyourheart.com/images/image-optimisation/melanie-pointing_alpha_halved_pal32_dithbayer_order2_thresh8-1-8.png" title="melanie-pointing_alpha_halved_pal32_dithbayer_order2_thresh8-1-8.png: 13.9kb"&gt;&lt;/p&gt;
&lt;p&gt;The three &amp;#8220;threshold&amp;#8221; parameters expected by hitherdither were a bit of a mystery to me. Through trial and error I found that some produced much smaller images, but unfortunately not at a level of quality that I found acceptable. Lower palette sizes also resulted in savings, but below 32 colours they started to look too abstract and unreadable to&amp;nbsp;me.&lt;/p&gt;
&lt;p&gt;Palette: 32, Dither: bayer, Threshold: 256/2-256-256/2, Order: 2, Size:&amp;nbsp;9.6kb&lt;/p&gt;
&lt;p&gt;&lt;img alt="melanie-pointing_alpha_halved_pal32_dithbayer_order2_thresh2-1-2.png" class="poi-no-optimise" src="https://blog.hyperlinkyourheart.com/images/image-optimisation/melanie-pointing_alpha_halved_pal32_dithbayer_order2_thresh2-1-2.png" title="melanie-pointing_alpha_halved_pal32_dithbayer_order2_thresh2-1-2.png: 9.6kb"&gt;&lt;/p&gt;
&lt;h2&gt;A Challenger&amp;nbsp;Appears&lt;/h2&gt;
&lt;p&gt;I was about ready to commit to this approach and start converting all the images when my wife reminded me that &lt;span class="caps"&gt;WEBP&lt;/span&gt; exists! After converting a few of my test images to &lt;span class="caps"&gt;WEBP&lt;/span&gt; it was clear that it had my dithered PNGs beat - half the size of the &lt;span class="caps"&gt;JPEG&lt;/span&gt; without any loss of&amp;nbsp;quality.&lt;/p&gt;
&lt;p&gt;80% quality &lt;span class="caps"&gt;WEBP&lt;/span&gt;, Size:&amp;nbsp;9.9kb&lt;/p&gt;
&lt;p&gt;&lt;img alt="melanie-pointing_alpha_baseline.webp" class="poi-no-optimise" src="https://blog.hyperlinkyourheart.com/images/image-optimisation/melanie-pointing_alpha_baseline.webp" title="melanie-pointing_alpha_baseline.webp: 9.9kb"&gt;&lt;/p&gt;
&lt;p&gt;Apparently &lt;a href="https://caniuse.com/?search=webp" title="WEBP support"&gt;support for &lt;span class="caps"&gt;WEBP&lt;/span&gt;&lt;/a&gt; is pretty good these days, but there are a couple of annoying outliers - Safari only supports it on Big Sur, and &lt;span class="caps"&gt;IE11&lt;/span&gt; still exists, as I&amp;#8217;m sure it always&amp;nbsp;will.&lt;/p&gt;
&lt;p&gt;As such, I decided I should probably try and fallback gracefully to a &lt;span class="caps"&gt;JPEG&lt;/span&gt; or &lt;span class="caps"&gt;PNG&lt;/span&gt; where &lt;span class="caps"&gt;WEBP&lt;/span&gt; isn&amp;#8217;t supported. This can be achieved using &lt;code&gt;&amp;lt;picture&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;source&amp;gt;&lt;/code&gt; elements to allow the browser to choose the format it likes&amp;nbsp;best.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;picture&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;source&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;image/webp&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;srcset&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;{optimal image url}&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;source&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;image/jpeg&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;srcset&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;{compatible image url}&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;img&lt;/span&gt; &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;{compatible image url}&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;picture&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Let&amp;#8217;s&amp;nbsp;Automate&lt;/h2&gt;
&lt;p&gt;The above &lt;span class="caps"&gt;HTML&lt;/span&gt; snippet presents a problem - my posts are not written in &lt;span class="caps"&gt;HTML&lt;/span&gt; but in Markdown, and processed by &lt;a href="https://blog.getpelican.com/" title="Pelican static site generator"&gt;Pelican&lt;/a&gt; into &lt;span class="caps"&gt;HTML&lt;/span&gt;, and that process just results in an &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; tag by&amp;nbsp;default.&lt;/p&gt;
&lt;p&gt;I threw together &lt;a href="https://github.com/khoulihan/pelican-optimise-images" title="pelican-optimise-images plugin"&gt;a quick Pelican plugin&lt;/a&gt; to post-process the generated &lt;span class="caps"&gt;HTML&lt;/span&gt; and replace any &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; tags with &lt;code&gt;&amp;lt;picture&amp;gt;&lt;/code&gt; tags, if the referenced images could be replaced with WEBPs. It also processes the referenced images to create scaled &lt;span class="caps"&gt;JPEG&lt;/span&gt;/&lt;span class="caps"&gt;PNG&lt;/span&gt; versions as well as the &lt;span class="caps"&gt;WEBP&lt;/span&gt; version, so I don&amp;#8217;t have to do any of that manually&amp;nbsp;either.&lt;/p&gt;
&lt;h2&gt;Results&lt;/h2&gt;
&lt;p&gt;The index of the blog is now 778kB at the time of writing - so reduced by over half! I did also replace a particularly large and troublesome &lt;span class="caps"&gt;GIF&lt;/span&gt; with a static image as well, and converted some PNGs to JPEGs. This results in greater savings because the plugin converts PNGs to lossless WEBPs and JPEGs to lossy&amp;nbsp;ones.&lt;/p&gt;
&lt;p&gt;The plugin is actually not even working fully yet - for some reason it is missing some &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; tags on the index pages, leaving them serving their original unprocessed&amp;nbsp;files.&lt;/p&gt;
&lt;p&gt;I also haven&amp;#8217;t done anything about the pixel art images, which if served at their original resolution could be a significant&amp;nbsp;saving.&lt;/p&gt;
&lt;p&gt;So in short, &lt;a href="https://www.websitecarbon.com/website/blog-hyperlinkyourheart-com/" title="76th percentile for energy efficiency"&gt;huge progress&lt;/a&gt;, but still much scope for&amp;nbsp;improvement!&lt;/p&gt;
&lt;p&gt;I am almost sorry I&amp;#8217;m not going to end up using these dithered PNGs&amp;nbsp;though&amp;#8230;&lt;/p&gt;
&lt;p&gt;&lt;img alt="Tracer and Chun-Li" class="poi-no-optimise" src="https://blog.hyperlinkyourheart.com/images/image-optimisation/tracer_alpha_halved_pal32_dithbayer_order2_thresh8-1-8.png" title="Wow looks like videogams"&gt;&lt;/p&gt;
&lt;h3&gt;Update&amp;nbsp;01/06/2021&lt;/h3&gt;
&lt;p&gt;I fixed the problem with the plugin and all images are now optimised except the few in this post that are flagged to be skipped. I also swapped out all the pixel art images and gifs for 1x resolution versions. These are just being scaled up by &lt;span class="caps"&gt;CSS&lt;/span&gt;, with reasonable&amp;nbsp;results.&lt;/p&gt;
&lt;p&gt;The index page is now 539kB, the whole site is just over &lt;span class="caps"&gt;1MB&lt;/span&gt;, and it is in the 80th percentile for energy usage according to websitecarbon.com (whatever that&amp;#8217;s worth). I think the link above showed 75th or 76th percentile or thereabouts at the time of posting, but it will show 80th&amp;nbsp;now.&lt;/p&gt;</content><category term="Meta"></category><category term="indieweb"></category><category term="blogging"></category></entry><entry><title>Energy Usage Update</title><link href="https://blog.hyperlinkyourheart.com/energy-usage-update.html" rel="alternate"></link><published>2021-05-27T13:20:00+02:00</published><updated>2025-06-12T15:23:00+02:00</updated><author><name>Kevin Houlihan</name></author><id>tag:blog.hyperlinkyourheart.com,2021-05-27:/energy-usage-update.html</id><summary type="html">&lt;p&gt;An update on the energy efficiency of this&amp;nbsp;blog.&lt;/p&gt;</summary><content type="html">&lt;p&gt;When I &lt;a href="https://blog.hyperlinkyourheart.com/remember-blogs.html"&gt;started this blog&lt;/a&gt;, one of my &lt;a href="https://blog.hyperlinkyourheart.com/embedding-svgs.html"&gt;goals&lt;/a&gt; was for it to be lean and efficient, both to improve the reader&amp;#8217;s experience and to reduce the power usage involved in serving&amp;nbsp;it.&lt;/p&gt;
&lt;p&gt;I&amp;#8217;ve been planning recently to write a post on a related topic - the energy efficiency of the Python language and how using it as the backend or &lt;span class="caps"&gt;API&lt;/span&gt; of a more dynamic website would fit with those same goals. In preparation for that, I thought to check in on this blog and whether it is still as lean as it was back when I was figuring out how to embed the &lt;span class="caps"&gt;SVG&lt;/span&gt; icons in&amp;nbsp;it.&lt;/p&gt;
&lt;p&gt;Unfortunately, it seems that it is not. Unlike one of my inspirations, &lt;a href="https://solar.lowtechmagazine.com/2018/09/how-to-build-a-lowtech-website.html"&gt;low-tech magazine&amp;#8217;s solar powered website&lt;/a&gt;, I never found a solution for reducing the size of image files. For my posts about &lt;span class="caps"&gt;TV&lt;/span&gt; shows and movies I use fairly low quality JPEGs. But for my art and game dev posts I have been unwilling to compromise, and, frankly, a bit lazy, and have just been throwing large PNGs and GIFs into my posts without thinking about their&amp;nbsp;size.&lt;/p&gt;
&lt;p&gt;As a result, loading the index of this site now transfers 1.&lt;span class="caps"&gt;7MB&lt;/span&gt; if nothing is already cached. The text content and &lt;span class="caps"&gt;CSS&lt;/span&gt; are in the tens of kilobytes, with the rest being images. This is comparable to the initial load of many corporate news sites and large blogs with all their tracking javascript and ads blocked - an improvement over the norm perhaps, since I&amp;#8217;m not doing any of the tracking or advertising that have become such a burden on the web, but not particularly notable. This site is now only in the &lt;a href="https://www.websitecarbon.com/website/blog-hyperlinkyourheart-com/"&gt;57th percentile according to websitecarbon.com&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;For pixel art, one solution does present itself immediately. For my &lt;a href="https://portfolio.hyperlinkyourheart.com"&gt;portfolio site&lt;/a&gt;, all of the artwork is sent to the browser at 1x resolution and resized there by an integer factor by a small amount of javascript, and perhaps the same thing could be achieved with css. The index of that site is under 400kB despite including all of my artwork, and does not require specially resized image thumbnails or anything like that. I could do the same thing here instead of throwing manually resized art into&amp;nbsp;posts.&lt;/p&gt;
&lt;p&gt;I&amp;#8217;m less sure what to do about screenshots and screencaps. The solution used by low-tech magazine doesn&amp;#8217;t really work for me, I think it compromises the readability of the images too much and doesn&amp;#8217;t fit this site&amp;#8217;s aesthetic. I tried a number of different techniques for vectorising, posterising and dithering screencaps from &lt;span class="caps"&gt;TV&lt;/span&gt; shows and movies to reduce the number of colours, but they didn&amp;#8217;t result in significant savings. On the other hand, they did actually look kind of cool and&amp;nbsp;stylised!&lt;/p&gt;
&lt;p&gt;&lt;img alt="Vectorised Beth Harmon" src="https://blog.hyperlinkyourheart.com/images/energy-usage-update/parkchesscrowd_vector.jpg" title="Vectorised Beth Harmon"&gt;&lt;/p&gt;
&lt;p&gt;Something I should probably try is scaling such images down significantly and then back up to the required display size in &lt;span class="caps"&gt;CSS&lt;/span&gt;. Obviously this will degrade the quality, but it might be an acceptable&amp;nbsp;trade-off.&lt;/p&gt;
&lt;p&gt;Screenshots of things like a text-editor or &lt;span class="caps"&gt;IDE&lt;/span&gt; are even more problematic - if the quality is degraded there they are probably not useful. I will probably just have to try to keep that kind of thing to a minimum, and use tighter crops where&amp;nbsp;necessary.&lt;/p&gt;
&lt;p&gt;It&amp;#8217;s easy to become complacent about efficiency when computers are so powerful that a few tens or hundreds of kilobytes don&amp;#8217;t seem to cause them any strain, but when you have that mindset they can quickly add up to quite a significant waste of time and power. I think this is well demonstrated by &lt;a href="https://daringfireball.net/linked/2018/05/27/usa-today-gdpr"&gt;&lt;span class="caps"&gt;USA&lt;/span&gt; Today&amp;#8217;s pissy response to the &lt;span class="caps"&gt;GDPR&lt;/span&gt;&lt;/a&gt;, where instead of the usual 5.&lt;span class="caps"&gt;2MB&lt;/span&gt; bloated garbage site, readers from the &lt;span class="caps"&gt;EU&lt;/span&gt; were served a lean, content focused site that loaded almost instantly and didn&amp;#8217;t track&amp;nbsp;them.&lt;/p&gt;
&lt;p&gt;Nothing I do is going to have the kind of impact that a site like &lt;span class="caps"&gt;USA&lt;/span&gt; Today improving their efficiency would have, purely because of the scale of the traffic involved, but I still want to try to do&amp;nbsp;better.&lt;/p&gt;</content><category term="Meta"></category><category term="indieweb"></category><category term="blogging"></category><category term="pixelart"></category></entry><entry><title>Ludum Dare 48 Results</title><link href="https://blog.hyperlinkyourheart.com/out-of-gas-results.html" rel="alternate"></link><published>2021-05-27T11:11:00+02:00</published><updated>2021-05-27T11:11:00+02:00</updated><author><name>Kevin Houlihan</name></author><id>tag:blog.hyperlinkyourheart.com,2021-05-27:/out-of-gas-results.html</id><summary type="html">&lt;p&gt;Results for Ludum Dare 48 are out, and I did less well than last&amp;nbsp;time!&lt;/p&gt;</summary><content type="html">&lt;p&gt;Ludum Dare 48 results are out a while now, but I forgot to post about them. I did not do as well as last time, but since my &lt;a href="https://blog.hyperlinkyourheart.com/gophers-results.html"&gt;results for Gophers&lt;/a&gt; were exceptional for me, that was probably to be expected. This time I placed 433rd overall, with the stand-out category being graphics, where I just about broke the top 100, at 99th&amp;nbsp;place.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style="text-align: left;"&gt;Category&lt;/th&gt;
&lt;th style="text-align: right;"&gt;Rating&lt;/th&gt;
&lt;th style="text-align: right;"&gt;Placing&lt;/th&gt;
&lt;th style="text-align: right;"&gt;Percentile&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;Overall&lt;/td&gt;
&lt;td style="text-align: right;"&gt;3.81&lt;/td&gt;
&lt;td style="text-align: right;"&gt;433&lt;/td&gt;
&lt;td style="text-align: right;"&gt;84th&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;Fun&lt;/td&gt;
&lt;td style="text-align: right;"&gt;3.259&lt;/td&gt;
&lt;td style="text-align: right;"&gt;1044&lt;/td&gt;
&lt;td style="text-align: right;"&gt;61st&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;Theme&lt;/td&gt;
&lt;td style="text-align: right;"&gt;3.672&lt;/td&gt;
&lt;td style="text-align: right;"&gt;873&lt;/td&gt;
&lt;td style="text-align: right;"&gt;67th&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;Innovation&lt;/td&gt;
&lt;td style="text-align: right;"&gt;3.345&lt;/td&gt;
&lt;td style="text-align: right;"&gt;754&lt;/td&gt;
&lt;td style="text-align: right;"&gt;72nd&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;Humor&lt;/td&gt;
&lt;td style="text-align: right;"&gt;3.596&lt;/td&gt;
&lt;td style="text-align: right;"&gt;277&lt;/td&gt;
&lt;td style="text-align: right;"&gt;89th&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;Graphics&lt;/td&gt;
&lt;td style="text-align: right;"&gt;4.466&lt;/td&gt;
&lt;td style="text-align: right;"&gt;99&lt;/td&gt;
&lt;td style="text-align: right;"&gt;96th&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;Audio&lt;/td&gt;
&lt;td style="text-align: right;"&gt;3.621&lt;/td&gt;
&lt;td style="text-align: right;"&gt;526&lt;/td&gt;
&lt;td style="text-align: right;"&gt;80th&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;Mood&lt;/td&gt;
&lt;td style="text-align: right;"&gt;3.846&lt;/td&gt;
&lt;td style="text-align: right;"&gt;460&lt;/td&gt;
&lt;td style="text-align: right;"&gt;83rd&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;Graphs&lt;/h2&gt;
&lt;p&gt;A couple of graphs demonstrating my &lt;em&gt;trends&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Ratings Graph" src="https://blog.hyperlinkyourheart.com/images/out-of-gas-results/overall_graphics_ratings.png"&gt;
&lt;img alt="Placings Graph" src="https://blog.hyperlinkyourheart.com/images/out-of-gas-results/placings.png"&gt;&lt;/p&gt;
&lt;p&gt;Still haven&amp;#8217;t surpassed that Rattendorf peak, &lt;em&gt;sigh&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;More Out of&amp;nbsp;Gas&lt;/h2&gt;
&lt;p&gt;I am committed to working on a post-jam release of this game, as I think with a few tweaks and a bit more content it could be something quite special (more special than the ratings above suggest&amp;nbsp;anyway!)&lt;/p&gt;
&lt;p&gt;I will be posting here with development updates, and also over on the &lt;a href="https://hyperlinkyourheart.itch.io/out-of-gas"&gt;itch.io&lt;/a&gt; page, where you can also still play the jam version for&amp;nbsp;now.&lt;/p&gt;</content><category term="Game Development"></category><category term="ludum-dare"></category><category term="sci-fi"></category><category term="music"></category><category term="pixelart"></category><category term="godot"></category></entry><entry><title>Out of Gas</title><link href="https://blog.hyperlinkyourheart.com/out-of-gas-post-mortem.html" rel="alternate"></link><published>2021-05-04T10:41:00+02:00</published><updated>2021-05-29T22:52:00+02:00</updated><author><name>Kevin Houlihan</name></author><id>tag:blog.hyperlinkyourheart.com,2021-05-04:/out-of-gas-post-mortem.html</id><summary type="html">&lt;p&gt;A post-mortem of my Ludum Dare 48 entry, Out of Gas - a space-road-trip themed narrative/clicker game about a couple of outlaws fleeing their cryptocurrency&amp;nbsp;debts.&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;a href="https://hyperlinkyourheart.itch.io/out-of-gas"&gt;&lt;img alt="Out of Gas" src="https://blog.hyperlinkyourheart.com/images/out-of-gas-post-mortem/OOG_itch_banner.jpg"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://hyperlinkyourheart.itch.io/out-of-gas" title="Out of Gas"&gt;Out of Gas&lt;/a&gt; is my entry for the recent &lt;a href="https://ldjam.com/events/ludum-dare/48" title="Ludum Dare 48"&gt;Ludum Dare 48&lt;/a&gt; game jam. It was intended to be a blatant &lt;a href="https://subsetgames.com/ftl.html" title="FTL game"&gt;&lt;span class="caps"&gt;FTL&lt;/span&gt;&lt;/a&gt; rip-off, but in having much simplified clicker-like combat mechanics and an unusual cars-in-space aesthetic, I think it is sufficiently its own thing (I hope&amp;nbsp;anyway!)&lt;/p&gt;
&lt;h2&gt;Concept&lt;/h2&gt;
&lt;p&gt;My initial thought was more of a straight sci-fi concept of travelling into deep space, facing combat and resource management challenges similar to those which ended up in the game. The first thing that came to mind as a title for this was &amp;#8220;Out of Gas&amp;#8221;, after the &lt;a href="https://www.youtube.com/watch?v=-_heR2ekoxI" title="Out of gas. Out of road. Out of car I don't know how I'm gonna go."&gt;Modest Mouse song&lt;/a&gt; of the same&amp;nbsp;name.&lt;/p&gt;
&lt;p&gt;Thinking about that song got me thinking in another direction - essentially the same mechanics, but Earth-bound, set in a Modest Mouse world of highways and drifters, fleeing problems and starting over. The problem to flee in this case would obviously be debt - something that you can get deeper and deeper&amp;nbsp;into.&lt;/p&gt;
&lt;p&gt;As you can probably tell by now I ended up combining these two ideas! I didn&amp;#8217;t want to let either of them go, and I quickly fell in love with the idea of a universe where space combat meant winding down your window and firing a handgun at your&amp;nbsp;enemy.&lt;/p&gt;
&lt;p&gt;The combined concept suggested that a new, sci-fi twist on the debt problem was required, and that&amp;#8217;s where the idea of a constantly growing cryptocurrency debt came in. I&amp;#8217;ve been thinking about cryptocurrencies a lot lately since they&amp;#8217;re going through another hype cycle. One commonly touted feature of them (or some, like Bitcoin, at least), is that they&amp;#8217;re deflationary, so any debt denominated in them would constantly grow in value instead of decreasing in value like debts denominated in inflationary fiat currencies. This seemed like the perfect notion for the bizarre world of my game. It doesn&amp;#8217;t really have any impact on the gameplay, but I think it&amp;#8217;s a nice bit of narrative flavour, and it fits the theme&amp;nbsp;perfectly.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Late payment" src="https://blog.hyperlinkyourheart.com/images/out-of-gas-post-mortem/OOG-Late-Payment.jpg"&gt;&lt;/p&gt;
&lt;p&gt;The final piece of the conceptual puzzle was the characters. I couldn&amp;#8217;t very well have two anonymous nobodies riding along with the player on a trip like this! I turned to two characters I had vague notions of making a game about years ago, Haze &lt;span class="amp"&gt;&amp;amp;&lt;/span&gt; Lee, a gun-toting, post-apocalyptic outlaw couple. Unfortunately I don&amp;#8217;t think their personalities really come across in the game due to time constraints, but at least they have&amp;nbsp;names!&lt;/p&gt;
&lt;h2&gt;Art&lt;/h2&gt;
&lt;p&gt;As usual, I did all the art in &lt;a href="https://www.pyxeledit.com/" title="Pyxel Edit"&gt;Pyxel Edit&lt;/a&gt;. I started off very rough with just pink boxes for the ships and a partial circle for the background, and moved immediately onto the gameplay with those placeholders. This is a somewhat unusual approach for me as I usually try to tie down the look of a game early&amp;nbsp;on.&lt;/p&gt;
&lt;p&gt;&lt;img alt="First mockup" src="https://blog.hyperlinkyourheart.com/images/out-of-gas-post-mortem/FirstMockup.png"&gt;&lt;/p&gt;
&lt;p&gt;The thing about rough mockups is that by the time I came back to the art a bunch of stuff in the game was already tied to the resolution and shape of the outlines. I wasn&amp;#8217;t even sure I had done the perspective correctly, but I was stuck with it anyway, and I guess it was close enough because nobody has&amp;nbsp;complained!&lt;/p&gt;
&lt;p&gt;&lt;img alt="First ships" src="https://blog.hyperlinkyourheart.com/images/out-of-gas-post-mortem/art_opt.jpg"&gt;&lt;/p&gt;
&lt;p&gt;I had planned to do some character portraits as well for the narrative side of the game, and express encounters as dialogues between characters, but unfortunately there wasn&amp;#8217;t time for&amp;nbsp;that.&lt;/p&gt;
&lt;p&gt;The map screen also didn&amp;#8217;t get much love, with the initial placeholder art surviving into the final&amp;nbsp;game.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Map screen" src="https://blog.hyperlinkyourheart.com/images/out-of-gas-post-mortem/OOG_Map_Trail.jpg"&gt;&lt;/p&gt;
&lt;h2&gt;Code&lt;/h2&gt;
&lt;p&gt;As for my last Ludum Dare entry, &lt;a href="https://blog.hyperlinkyourheart.com/gophers-post-mortem.html"&gt;Gophers&lt;/a&gt;, a &lt;a href="https://github.com/khoulihan/godot-cutscene-graph" title="Cutscene Graph Editor"&gt;cutscene graph editor plugin&lt;/a&gt; for &lt;a href="https://godotengine.org/" title="The game engine you waited for."&gt;Godot&lt;/a&gt; that I had previously developed was essential to getting this game done within the time constraints of the&amp;nbsp;jam.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Those darn teenagers!" src="https://blog.hyperlinkyourheart.com/images/out-of-gas-post-mortem/teenagers_dialogue.jpg"&gt;&lt;/p&gt;
&lt;p&gt;I had done a lot of work on this plugin in recent months, applying lessons learned from using it for Gophers to improve the workflow, functionality, and stability. This paid huge dividends, and I found almost no issues while churning out encounter cutscenes. The cutscene graphs created by this plugin power every encounter in the game up to and after&amp;nbsp;combat.&lt;/p&gt;
&lt;p&gt;My main task on the first day was to get the map screen working - it wouldn&amp;#8217;t be much of a game if you couldn&amp;#8217;t travel between systems. I implemented it as a graph defined by custom &lt;code&gt;MapSystem&lt;/code&gt; and &lt;code&gt;MapConnection&lt;/code&gt; control&amp;nbsp;nodes.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Map graph" src="https://blog.hyperlinkyourheart.com/images/out-of-gas-post-mortem/map_ui.jpg"&gt;&lt;/p&gt;
&lt;p&gt;The new fun thing about this for me was declaring the &lt;code&gt;MapConnection&lt;/code&gt; class to be a &amp;#8220;tool&amp;#8221; script, allowing the connections between systems to be drawn in the editor. This kind of custom tooling is important for the efficient design of systems in larger games, so it was fun to give it a try. Here, it mostly served to prevent me from getting confused about which &lt;code&gt;MapSystem&lt;/code&gt; nodes I had already connected to each other and which I&amp;nbsp;hadn&amp;#8217;t.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;func&lt;/span&gt; &lt;span class="n"&gt;_draw&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;usable&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;used&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;_can_draw_in_editor&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="n"&gt;_load_nodes_for_editor&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="c1"&gt;# TODO: Decide on proper colours for this&lt;/span&gt;
        &lt;span class="n"&gt;var&lt;/span&gt; &lt;span class="n"&gt;color&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Color&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;gray&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;usable&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;color&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Color&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hotpink&lt;/span&gt;
        &lt;span class="c1"&gt;# No idea why the offset is required&lt;/span&gt;
        &lt;span class="n"&gt;var&lt;/span&gt; &lt;span class="n"&gt;source_center&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_source_node&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rect_global_position&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;CENTER_OFFSET&lt;/span&gt;
        &lt;span class="n"&gt;var&lt;/span&gt; &lt;span class="n"&gt;target_center&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_target_node&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rect_global_position&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;CENTER_OFFSET&lt;/span&gt;
        &lt;span class="n"&gt;var&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;source_center&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;target_center&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;source_center&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;normalized&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;SPACE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;var&lt;/span&gt; &lt;span class="n"&gt;finish&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;target_center&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;source_center&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;target_center&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;normalized&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;SPACE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;draw_line&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;finish&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;color&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;LINE_THICKNESS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;false&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Implementing the combat was a lot tougher. The battle scene is almost entirely &lt;span class="caps"&gt;UI&lt;/span&gt; nodes, which was perhaps a mistake, and for some reason I struggled most of day two to even get player input to register properly. I don&amp;#8217;t really remember now what the obstacle was to this - maybe I was just&amp;nbsp;tired.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Battle UI" src="https://blog.hyperlinkyourheart.com/images/out-of-gas-post-mortem/encounter_ui.jpg"&gt;&lt;/p&gt;
&lt;h2&gt;Sound and&amp;nbsp;Music&lt;/h2&gt;
&lt;p&gt;There&amp;#8217;s nothing special about the sound this time around. Unlike for Gophers I didn&amp;#8217;t have time to do any foley work, and I figured I would probably have a hard time creating gunshot and explosion sounds anyway. Instead I fell back to &lt;span class="caps"&gt;SFXR&lt;/span&gt; (specifically, &lt;a href="https://sfxr.me/" title="jsfxr"&gt;jsfxr&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;The music I put together in &lt;a href="https://beepbox.co" title="BeepBox"&gt;BeepBox&lt;/a&gt;, my favourite game jam tool for chiptune music these days due to its simplicity. I tend not to get as bogged down with it as I do with &lt;a href="https://lmms.io/" title="LMMS"&gt;&lt;span class="caps"&gt;LMMS&lt;/span&gt;&lt;/a&gt;. I initially wanted to try to create something in the style of Modest Mouse, given the game&amp;#8217;s inspiration, but it was hard to recreate a guitar sound. Instead I ended up with a simple arpeggiated chord progression over a bass drone that I hope feels kind of epic, kind of longing and lonesome, I&amp;nbsp;dunno.&lt;/p&gt;
&lt;p&gt;The battle music is a variation of the same tune but just swaps out one of the instruments, doubles up the drone, and compresses and spreads the arpeggios across more octaves for a more urgent and chaotic feeling. The two tracks play in sync throughout the game and just cut between each other as&amp;nbsp;necessary.&lt;/p&gt;
&lt;h2&gt;Abandoned&amp;nbsp;Ideas&lt;/h2&gt;
&lt;p&gt;As usual there were many ideas that I had to put aside due to time constraints. I had planned to include a small number of missiles which could be fired during combat with devastating effect, to allow the player to pull themselves back from the brink of defeat. I had also intended to have targetable/damageable systems on the ships, including injury to the characters, so that you could take out the enemy&amp;#8217;s weapon to gain a reprieve from being fired upon, or their engines and reduce their chance to dodge. You can see the proposed &lt;span class="caps"&gt;UI&lt;/span&gt; of these mechanics in the initial mockup above. Finally, I had planned a system of weapon upgrades which almost made it in, but not&amp;nbsp;quite.&lt;/p&gt;
&lt;p&gt;On the art side, I had wanted to include a number of different backgrounds to improve the sense of travelling between&amp;nbsp;systems.&lt;/p&gt;
&lt;p&gt;A big regret that I have is that I wasn&amp;#8217;t able to include the character portraits, and express the encounters primarily as dialogue between characters. I think this would have introduced a lot more personality to the narrative side of the game, as opposed to the third-person narration that I had to go&amp;nbsp;with.&lt;/p&gt;
&lt;h2&gt;Post-Jam&lt;/h2&gt;
&lt;p&gt;I like this game a lot, and I feel it is one that could be polished up and rounded out into a complete experience with relative ease, so I am determined to do a post-jam release with some of the missing features described above, and some of the problems with the jam release resolved (such as the &lt;span class="caps"&gt;RNG&lt;/span&gt; sometimes producing the same encounter multiple times in a row). I have already begun work on this, and I hope to have it completed in the next few months. I&amp;#8217;ll do another post about my progress on it so far, but for now here&amp;#8217;s a little sneak&amp;nbsp;peek:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://hyperlinkyourheart.itch.io/out-of-gas"&gt;&lt;img alt="Haze speaks!" src="https://blog.hyperlinkyourheart.com/images/out-of-gas-post-mortem/HazeSpeaks.jpg"&gt;&lt;/a&gt;&lt;/p&gt;</content><category term="Game Development"></category><category term="ludum-dare"></category><category term="sci-fi"></category><category term="music"></category><category term="pixelart"></category><category term="godot"></category></entry><entry><title>Coroutine Callbacks</title><link href="https://blog.hyperlinkyourheart.com/coroutine-callbacks.html" rel="alternate"></link><published>2021-04-05T18:29:00+02:00</published><updated>2021-04-05T18:29:00+02:00</updated><author><name>Kevin Houlihan</name></author><id>tag:blog.hyperlinkyourheart.com,2021-04-05:/coroutine-callbacks.html</id><summary type="html">&lt;p&gt;Using coroutines to call back to the emitter of a&amp;nbsp;signal.&lt;/p&gt;</summary><content type="html">&lt;p&gt;I&amp;#8217;m working on a cutscene/dialogue graph editor plugin for the &lt;a href="https://godotengine.org/" title="The game engine you waited for."&gt;Godot game engine&lt;/a&gt; (I mentioned using it for the Ludum Dare in a &lt;a href="https://blog.hyperlinkyourheart.com/gophers-post-mortem.html"&gt;previous post&lt;/a&gt;). When I came to deciding how to actually process the graphs it creates in order to display the dialogue and perform actions in the game, I discovered an interesting use for coroutines that I haven&amp;#8217;t seen described elsewhere. I&amp;#8217;m not all that used to working with coroutines so maybe what I&amp;#8217;ve done is completely normal and everybody does it all the time, or indeed it might be ill-advised for some reason. Either way, I will describe it here and you can ignore me if I&amp;#8217;m being an&amp;nbsp;idiot!&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/khoulihan/godot-cutscene-graph" title="Cutscene Graph Editor"&gt;&lt;img alt="Graph editor" src="https://blog.hyperlinkyourheart.com/images/gophers-post-mortem/graph.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Processing a graph that results in activity in-game is a task which frequently needs to be put on hold - waiting for text to be displayed in the &lt;span class="caps"&gt;UI&lt;/span&gt; in a juicy manner, moving characters around, waiting for input etc. This could be achieved in a &lt;code&gt;Node&lt;/code&gt; by implementing &lt;code&gt;_process()&lt;/code&gt; and maintaining state about whether you&amp;#8217;re currently walking a graph or waiting on something, and having methods that other nodes can use to indicate when processing can continue. I have found such methods to be quite messy in the&amp;nbsp;past.&lt;/p&gt;
&lt;p&gt;I decided instead to process the graph in a &lt;code&gt;while&lt;/code&gt; loop and &lt;code&gt;yield&lt;/code&gt; as necessary while other nodes perform tasks initiated by a number of signals. Where tasks are expected to take some time to complete, a coroutine state object is passed to the signal which the listener can use to indicate that processing should continue, or in some cases return&amp;nbsp;values.&lt;/p&gt;
&lt;h2&gt;Let&amp;#8217;s See Some&amp;nbsp;Code&lt;/h2&gt;
&lt;p&gt;Below is an abridged version of the main method that processes the graphs. Processing a dialogue node yields so that the &lt;span class="caps"&gt;UI&lt;/span&gt; that displays the text can wait for player input to continue. Processing a branch node and others like it just proceed directly to the next iteration of the&amp;nbsp;loop.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;func&lt;/span&gt; &lt;span class="n"&gt;process_cutscene&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cutscene&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;_local_store&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
    &lt;span class="n"&gt;_current_graph&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cutscene&lt;/span&gt;
    &lt;span class="n"&gt;_current_node&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_current_graph&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;root_node&lt;/span&gt;
    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;_current_node&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;null&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;_current_node&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="n"&gt;DialogueTextNode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;yield&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_process_dialogue_node&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;completed&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;_current_node&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="n"&gt;BranchNode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;_process_branch_node&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="o"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Here is the code that processes dialogue nodes and raises the signal for their text to be displayed in-game. A coroutine is put in a frozen state to be passed with the signal, and the call to emit the signal is deferred so we can make sure we are waiting for the coroutine to complete before any signal listeners have a chance to resume&amp;nbsp;it.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;func&lt;/span&gt; &lt;span class="n"&gt;_await_response&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;yield&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;


&lt;span class="n"&gt;func&lt;/span&gt; &lt;span class="n"&gt;_emit_dialogue_signal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;character_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;character_variant&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;process&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;emit_signal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;dialogue_display_requested&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;character_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;character_variant&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;process&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="n"&gt;func&lt;/span&gt; &lt;span class="n"&gt;_process_dialogue_node&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;var&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_current_node&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;

    &lt;span class="n"&gt;var&lt;/span&gt; &lt;span class="n"&gt;process&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_await_response&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;call_deferred&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;_emit_dialogue_signal&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;_current_node&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;character&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;character_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;_current_node&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;character_variant&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;variant_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;process&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;yield&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;process&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;completed&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;_current_node&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_current_node&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;next&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The code that consumes the signal can take however long it likes to do its thing, and when it&amp;#8217;s ready it can just call &lt;code&gt;resume()&lt;/code&gt; on the coroutine state and the graph processing will proceed to the next&amp;nbsp;node.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;func&lt;/span&gt; &lt;span class="n"&gt;_on_CutsceneController_dialogue_display_requested&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;character_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;character_variant&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;process&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;_process&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;process&lt;/span&gt;
    &lt;span class="o"&gt;...&lt;/span&gt;


&lt;span class="n"&gt;func&lt;/span&gt; &lt;span class="n"&gt;_on_ContinueButton_pressed&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;_process&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;null&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;_process&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;resume&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;If a node in the graph needs a response in order to know what to do next (e.g. a choice between multiple dialogue options), we can just accept the return value of the coroutine passed in the signal. The consumer can just pass the value to the &lt;code&gt;resume()&lt;/code&gt; call.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;func&lt;/span&gt; &lt;span class="n"&gt;_process_choice_node&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
  &lt;span class="o"&gt;...&lt;/span&gt;
    &lt;span class="n"&gt;var&lt;/span&gt; &lt;span class="n"&gt;process&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_await_response&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;call_deferred&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;_emit_choices_signal&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;choices&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;process&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;var&lt;/span&gt; &lt;span class="n"&gt;choice&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;yield&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;process&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;completed&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;_current_node&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_current_node&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;branches&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;choice&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Potential&amp;nbsp;Pitfalls&lt;/h2&gt;
&lt;p&gt;There are two pitfalls that come to mind with this approach, but they&amp;#8217;re not really any different than what could occur with a &lt;code&gt;_process()&lt;/code&gt; based approach like the one I described&amp;nbsp;above.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Processing of another graph could be triggered while one is still in progress. If the previous one is yielding it would end up processing the new graph as well when it resumes. This is quite easy to avoid by just noping out of the method if there is already work in&amp;nbsp;progress.&lt;/li&gt;
&lt;li&gt;If there are multiple listeners connected to the signals there could be ambiguity about which one is responsible for resuming. If there are multiple resume calls, all but the first will result in a runtime&amp;nbsp;exception.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;In the event that you actually &lt;em&gt;want&lt;/em&gt; multiple listeners to report in before resuming, it might be possible to craft a different coroutine that takes the &lt;a href="https://docs.godotengine.org/en/stable/classes/class_object.html#class-object-method-get-signal-connection-list" title="get_signal_connection_list()"&gt;number of listeners&lt;/a&gt; and yields until they have all resumed. I think it would look something like&amp;nbsp;this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;func&lt;/span&gt; &lt;span class="n"&gt;_await_many&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
  &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;yield&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="o"&gt;-=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;


&lt;span class="n"&gt;func&lt;/span&gt; &lt;span class="n"&gt;_await_many_responses&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
  &lt;span class="n"&gt;var&lt;/span&gt; &lt;span class="n"&gt;responses&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
  &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;responses&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;yield&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="o"&gt;-=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;responses&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;I haven&amp;#8217;t tested that&amp;nbsp;though.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;I don&amp;#8217;t really have one, I just thought it was a neat pattern that doesn&amp;#8217;t require the listeners to keep a reference to the node doing the processing or have any knowledge of it aside from the signals it emits. You can check out &lt;a href="https://github.com/khoulihan/godot-cutscene-graph" title="Cutscene Graph Editor"&gt;the full code on GitHub&lt;/a&gt; as it stands currently. I haven&amp;#8217;t tested it in a game yet, but I will probably enter the &lt;a href="https://ldjam.com/" title="Ludum Dare 48"&gt;Ludum Dare&lt;/a&gt; later this month and I might get a chance to use it then. I will update afterwards whether it is a disaster or a&amp;nbsp;triumph!&lt;/p&gt;</content><category term="Game Development"></category><category term="godot"></category><category term="gdscript"></category></entry><entry><title>America Wins Again</title><link href="https://blog.hyperlinkyourheart.com/queens-gambit.html" rel="alternate"></link><published>2020-12-21T20:27:00+01:00</published><updated>2020-12-31T15:33:00+01:00</updated><author><name>Kevin Houlihan</name></author><id>tag:blog.hyperlinkyourheart.com,2020-12-21:/queens-gambit.html</id><summary type="html">&lt;p&gt;Or does&amp;nbsp;it?&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;img alt="Chess lives here" src="https://blog.hyperlinkyourheart.com/images/queens-gambit/chesshall.jpg" title="Chess lives here"&gt;&lt;/p&gt;
&lt;p&gt;&lt;i class="fas fa-exclamation-triangle spoiler-icon"&gt;&lt;/i&gt;&lt;span class="spoiler-text"&gt;This post contains spoilers for the &lt;span class="caps"&gt;TV&lt;/span&gt; show &amp;#8220;The Queen&amp;#8217;s&amp;nbsp;Gambit&amp;#8221;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;I loved pretty much everything about The Queen&amp;#8217;s Gambit, and as you&amp;#8217;ve probably gathered if you&amp;#8217;ve read any of my other posts, I don&amp;#8217;t have much to say about things if I&amp;#8217;m not complaining about them! &lt;i class="far fa-laugh-squint body-icon"&gt;&lt;/i&gt;&lt;/p&gt;
&lt;p&gt;What I do feel obligated to talk about, however, are the differences between this show and &amp;#8220;For All Mankind&amp;#8221; in terms of how they deal with the Soviet Union. I&amp;#8217;ve &lt;a href="https://blog.hyperlinkyourheart.com/for-all-mankind.html"&gt;written previously&lt;/a&gt; about how &amp;#8220;For All Mankind&amp;#8221; re-imagines the space race such that the &lt;span class="caps"&gt;USA&lt;/span&gt; remains the underdog after several successful Soviet moon landings - erasing real Soviet accomplishments in favour of fictional ones, and providing an impetus for the &lt;span class="caps"&gt;US&lt;/span&gt; to include women in its space program, something that it didn&amp;#8217;t do in reality until the&amp;nbsp;1980&amp;#8217;s.&lt;/p&gt;
&lt;p&gt;The Queen&amp;#8217;s Gambit does not rewrite history to the same extent. The chess world was dominated by Soviet players in the 50s, 60s and beyond, and the show acknowledges that readily, with &amp;#8220;The Russian&amp;#8221;, Vasily Borgov, being Beth Harmon&amp;#8217;s ultimate opponent. Her breakthrough somewhat reflects that of American prodigy Bobby Fischer, so it&amp;#8217;s not unprecedented. However, where For All Mankind featured a number of real-life historical figures, all the competitors in The Queen&amp;#8217;s Gambit are&amp;nbsp;fictional.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Team USA" src="https://blog.hyperlinkyourheart.com/images/queens-gambit/teamusa.jpg" title="Team USA"&gt;&lt;/p&gt;
&lt;p&gt;In a way, however, Beth&amp;#8217;s real opponent is not Borgov or any of the other men she defeats on the road to face him, but herself, her emotional problems, and her addictions. The games she plays are almost entirely without malice, with just a touch of smug arrogance on occasion, and everybody she faces ends up with enormous admiration for her. This is especially true of the Soviet players she faces, who almost seem happier to have been beaten by her than they would have been to have won. What cold war political animosity is present comes mostly from her &lt;span class="caps"&gt;CIA&lt;/span&gt; handler, and is treated as a ridiculous, petty distraction by Beth. Doesn&amp;#8217;t he know that there&amp;#8217;s &lt;em&gt;chess&lt;/em&gt; to be&amp;nbsp;played??&lt;/p&gt;
&lt;p&gt;For All Mankind, in contrast, has that animosity at its core. Its the motivation behind all of the American government&amp;#8217;s actions in the show, though not necessarily of everybody at &lt;span class="caps"&gt;NASA&lt;/span&gt;, while the one Soviet character seems to validate their&amp;nbsp;suspicions.&lt;/p&gt;
&lt;p&gt;The Queen&amp;#8217;s Gambit&amp;#8217;s portrayal of the Soviet Union is actually extraordinarily sympathetic as a result of being viewed through the lens of chess enthusiasm. Whereas at home chess is a niche interest, when Beth arrives in the &lt;span class="caps"&gt;USSR&lt;/span&gt; she discovers that it is a national obsession. At home her prospects are probably akin to those of Benny Watts, the &lt;span class="caps"&gt;US&lt;/span&gt; chess champion before her - obscurity, and a dingy basement apartment at best. In the &lt;span class="caps"&gt;USSR&lt;/span&gt; she is mobbed by an adoring crowd after every match, which she plays in a dedicated chess hall instead of whatever spaces are available. She even has to adopt their strategy of cooperating during adjournments before she is able to achieve victory, with a team of all the chess friends she&amp;#8217;s acquired on her journey advising her on how to approach the rest of the game - an apparent admission of the superiority of collective cooperation over competition and&amp;nbsp;individualism.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Welcome home" src="https://blog.hyperlinkyourheart.com/images/queens-gambit/parkchesscrowd.jpg" title="Welcome home"&gt;&lt;/p&gt;
&lt;p&gt;The final scene sees Beth slipping her handler&amp;#8217;s grasp on the way to the airport. She wanders the streets of Moscow unmolested to find the old guys playing chess in the park, and they greet her warmly before inviting her to play. There&amp;#8217;s a distinct sense that she has found a home here, where the game she loves is played openly in the park instead of hidden away in the basement, as in the orphanage where she grew&amp;nbsp;up.&lt;/p&gt;
&lt;p&gt;Although this series is still about an American character and mostly set in America, it is much closer to being the view from &amp;#8220;the other side&amp;#8221; that I hoped for in my previous&amp;nbsp;post.&lt;/p&gt;</content><category term="TV"></category><category term="not-a-tankie-but"></category><category term="politics"></category></entry><entry><title>Cybercentrism</title><link href="https://blog.hyperlinkyourheart.com/cybercentrism.html" rel="alternate"></link><published>2020-12-12T19:26:00+01:00</published><updated>2020-12-13T18:10:00+01:00</updated><author><name>Kevin Houlihan</name></author><id>tag:blog.hyperlinkyourheart.com,2020-12-12:/cybercentrism.html</id><summary type="html">&lt;p&gt;Ready Player One successfully merges cyberpunk with centrist politics, for some&amp;nbsp;reason.&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;img alt="You're a wizard Wade!" src="https://blog.hyperlinkyourheart.com/images/cybercentrism/pass2.jpeg" title="You're a wizard Wade!"&gt;&lt;/p&gt;
&lt;p&gt;&lt;i class="fas fa-exclamation-triangle spoiler-icon"&gt;&lt;/i&gt;&lt;span class="spoiler-text"&gt;This post contains spoilers for the movie &amp;#8220;Ready Player&amp;nbsp;One&amp;#8221;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;I finally gave in and watched Ready Player One, prompted by seeing the Internet dump on the sequel to the novel. I avoided it before now because the commentary on the novel made it sound like pandering nonsense, and I assumed the movie would be much the same. But, I wanted to see for&amp;nbsp;myself.&lt;/p&gt;
&lt;p&gt;Perhaps as a result of those lowered expectations, I actually found it quite entertaining on a surface level. The pop culture references were certainly omnipresent, but maybe not as tiresome as having them explained in writing would be. In movie form, a lot of them that are not plot-relevant just form a visual backdrop that actually help define a plausible metaverse, albeit one with a culture that is inexplicable stuck 30 years in the&amp;nbsp;past.&lt;/p&gt;
&lt;p&gt;But on the other hand, the centrality of pop culture to the plot, and why it is central, is also what leaves a bad taste in my mouth. In the universe of Ready Player One, the online world is dominated by a metaverse-like virtual reality game called the &lt;span class="caps"&gt;OASIS&lt;/span&gt;, a game owned and controlled by a single corporation, and ultimately a single man, its creator, James Halliday. When he dies, he decrees that his successor will be decided by a tripartite fetch quest within the virtual world. In a monumental act of narcissism, the quest&amp;#8217;s challenges are not based on the skills required to run a massive company or a piece of essential infrastructure, or tests of morality or wisdom, but on intimate knowledge of his own life and his pop cultural obsessions. This might be an interesting setup if Halliday were the antagonist, but he&amp;#8217;s not, he&amp;#8217;s practically worshipped by the users of the &lt;span class="caps"&gt;OASIS&lt;/span&gt;.&lt;/p&gt;
&lt;h2&gt;Wow!! Cool&amp;nbsp;Future!!&lt;/h2&gt;
&lt;p&gt;This movie has the trappings of cyberpunk, but little of the critique. Victory in this story is not the smashing of a corporate monopoly or frustrating of its goals, but the passing of the reins of power from a virtuous benevolent founder to a handful of virtuous disciples, through an absurd faux-meritocratic process where merit means liking the right books and games and&amp;nbsp;movies.&lt;/p&gt;
&lt;p&gt;The antagonist, Sorrento, is the &lt;span class="caps"&gt;CEO&lt;/span&gt; of a company that runs debtor&amp;#8217;s prisons, yet somehow the movie gives the impression that his greater sin is being a poseur who&amp;#8217;s only pretending to &amp;#8220;get&amp;#8221; the stuff that Halliday liked. The denouement even reveals that Halliday could have used his monopoly power to end the practice of indentured servitude at any time, but apparently chose not to, or just didn&amp;#8217;t think to, and this is not framed as a critique of monopoly capitalism, but is just a throwaway line to demonstrate how benevolent the protagonist is in his new&amp;nbsp;role.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Remember Tracer? Remember Chun-Li? Remember various skeletons?" src="https://blog.hyperlinkyourheart.com/images/cybercentrism/tracer.jpeg" title="Remember Tracer? Remember Chun-Li? Remember various skeletons?"&gt;&lt;/p&gt;
&lt;p&gt;The third act involves an epic battle in the &lt;span class="caps"&gt;OASIS&lt;/span&gt;, with the user-base coming together to fight Sorrento&amp;#8217;s forces and prevent them from completing the quest. It&amp;#8217;s presented as an empowering grass-roots uprising to save the &lt;span class="caps"&gt;OASIS&lt;/span&gt; from an evil corporation, and fine, they are pretty evil&amp;#8230; But the status quo is also intolerable. They&amp;#8217;re not fighting for their own empowerment, but merely to keep the unaccountable corporate power that dominates their lives out of even more abusive hands. It&amp;#8217;s a pretty uninspiring&amp;nbsp;cause.&lt;/p&gt;
&lt;p&gt;The movie&amp;#8217;s true feelings on the nature of power, and the relationship of ordinary people to it, is revealed most starkly at its climax. The conflict in the &lt;span class="caps"&gt;OASIS&lt;/span&gt; has spilled over into the real world, with our intrepid heroes being chased through the streets in a van, exposed at every turn by &lt;span class="caps"&gt;CCTV&lt;/span&gt; tracking technology and drones. While Sorrento sets out to confront them personally, they put out a call for aid, for their supporters to gather in the slums and defend them. When Sorrento reaches them, a huge crowd of people stream out of the stacks to oppose&amp;nbsp;him.&lt;/p&gt;
&lt;p&gt;&lt;img alt="It's dangerous to go alone! Take this!" src="https://blog.hyperlinkyourheart.com/images/cybercentrism/takethis.jpeg" title="It's dangerous to go alone! Take this!"&gt;&lt;/p&gt;
&lt;p&gt;For a moment, it seems as though the plot really is going to be resolved by collective direct action. &lt;em&gt;People power, woo!&lt;/em&gt; Instead, Sorrento pulls out a gun, and the crowd parts. They could easily disarm him, there are hundreds of them all around him. Instead they gawp uselessly at him, right up to the point where he is threatening to shoot some children in the face. It is only the police arriving that prevents him from committing murder right in front of&amp;nbsp;them.&lt;/p&gt;
&lt;p&gt;With Sorrento out of the way thanks to the police, a bunch of lawyers arrive to certify the protagonist&amp;#8217;s completion of the quest and acquisition of complete control over the &lt;span class="caps"&gt;OASIS&lt;/span&gt;. The crowd are reduced to placid spectators in a boardroom drama that doesn&amp;#8217;t involve them or empower them. This is, I feel, the perfect encapsulation of the movie&amp;#8217;s worldview. The crowd are the audience - expected to worship and trust benevolent CEOs, to react strongly in defence of a commodified culture where they are otherwise passive consumers, but to look on silently and not interfere as control over that culture changes hands, maintaining the status&amp;nbsp;quo.&lt;/p&gt;</content><category term="Movies"></category><category term="fiction"></category><category term="politics"></category></entry><entry><title>Avocados</title><link href="https://blog.hyperlinkyourheart.com/avocados.html" rel="alternate"></link><published>2020-11-02T21:23:00+01:00</published><updated>2025-06-08T18:04:00+02:00</updated><author><name>Kevin Houlihan</name></author><id>tag:blog.hyperlinkyourheart.com,2020-11-02:/avocados.html</id><summary type="html">&lt;p&gt;A couple of juicy&amp;nbsp;avocados.&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;a href="https://portfolio.hyperlinkyourheart.com/avocados.html"&gt;&lt;img alt="Avocados" src="https://blog.hyperlinkyourheart.com/images/avocados/Avocado02_x1.png" title="Avocados"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I&amp;#8217;m not very good at doing these art posts in a timely manner; I did this still-life of a couple of avocados for pixel dailies back in&amp;nbsp;September!&lt;/p&gt;
&lt;h2&gt;Timelapse&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=QP35xoMXE8U"&gt;&lt;img alt="Avocados" src="https://img.youtube.com/vi/QP35xoMXE8U/0.jpg"&gt;&lt;/a&gt;&lt;/p&gt;</content><category term="Art"></category><category term="pixelart"></category><category term="still-life"></category></entry><entry><title>Mine</title><link href="https://blog.hyperlinkyourheart.com/mine.html" rel="alternate"></link><published>2020-09-26T23:29:00+02:00</published><updated>2021-05-30T17:25:00+02:00</updated><author><name>Kevin Houlihan</name></author><id>tag:blog.hyperlinkyourheart.com,2020-09-26:/mine.html</id><summary type="html">&lt;p&gt;A portrait of my friend&amp;nbsp;Mine.&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;a href="https://portfolio.hyperlinkyourheart.com/mine.html"&gt;&lt;img alt="Mine" src="https://blog.hyperlinkyourheart.com/images/mine/Mine_270x_x1.png" title="Mine"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;A portrait I did of my friend &lt;a href="https://www.instagram.com/flamenclorca/"&gt;Mine&lt;/a&gt; a few weeks&amp;nbsp;ago.&lt;/p&gt;
&lt;h2&gt;Timelapse&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=rbd8nfxpNxY"&gt;&lt;img alt="Mine" src="https://img.youtube.com/vi/rbd8nfxpNxY/0.jpg"&gt;&lt;/a&gt;&lt;/p&gt;</content><category term="Art"></category><category term="pixelart"></category><category term="portrait"></category></entry><entry><title>Anticipation</title><link href="https://blog.hyperlinkyourheart.com/anticipation.html" rel="alternate"></link><published>2020-09-09T12:19:00+02:00</published><updated>2021-05-30T17:25:00+02:00</updated><author><name>Kevin Houlihan</name></author><id>tag:blog.hyperlinkyourheart.com,2020-09-09:/anticipation.html</id><summary type="html">&lt;p&gt;A dog waiting for a very important&amp;nbsp;letter.&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;a href="https://portfolio.hyperlinkyourheart.com/anticipation.html"&gt;&lt;img alt="Anticipation" src="https://blog.hyperlinkyourheart.com/images/anticipation/AnticipationPortrait11Animated_x1_Optimised.gif" title="Anticipation"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I was inspired to create this when I won a postcard and sticker pack giveaway on twitter by my friend &lt;a href="https://twitter.com/moertel/"&gt;Stefanie&lt;/a&gt;, and I was eagerly waiting for it to arrive! I actually did this a while ago but I forgot to post it&amp;nbsp;here.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Postcard and stickers" src="https://blog.hyperlinkyourheart.com/images/anticipation/pack.jpg" title="Postcard and stickers"&gt;&lt;/p&gt;
&lt;p&gt;Go check out Stefanie&amp;#8217;s work, &lt;a href="https://moer.tel/"&gt;it&amp;#8217;s amazing&lt;/a&gt;. Thanks for the goodies&amp;nbsp;Stef!&lt;/p&gt;
&lt;h2&gt;Timelapse&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=IdL1mG59ApU"&gt;&lt;img alt="Anticipation" src="https://img.youtube.com/vi/IdL1mG59ApU/0.jpg"&gt;&lt;/a&gt;&lt;/p&gt;</content><category term="Art"></category><category term="pixelart"></category><category term="animation"></category></entry><entry><title>Stars Trek - Failures of Imagination</title><link href="https://blog.hyperlinkyourheart.com/picard.html" rel="alternate"></link><published>2020-09-08T23:29:00+02:00</published><updated>2020-09-08T23:29:00+02:00</updated><author><name>Kevin Houlihan</name></author><id>tag:blog.hyperlinkyourheart.com,2020-09-08:/picard.html</id><summary type="html">&lt;p&gt;Wherein I take Star Trek: Picard far too&amp;nbsp;seriously.&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;img alt="Where did it all go wrong?" src="https://blog.hyperlinkyourheart.com/images/picard/picard-at-the-bar.jpg" title="Where did it all go wrong?"&gt;&lt;/p&gt;
&lt;p&gt;&lt;i class="fas fa-exclamation-triangle spoiler-icon"&gt;&lt;/i&gt;&lt;span class="spoiler-text"&gt;This post contains spoilers for the &lt;span class="caps"&gt;TV&lt;/span&gt; show&amp;nbsp;&amp;#8220;Picard&amp;#8221;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Star Trek: The Next Generation, and its follow-ups, Deep Space 9 and Voyager, are some of my favourite &lt;span class="caps"&gt;TV&lt;/span&gt; shows. They are set in a future post-scarcity utopia where poverty, disease and war (amongst humans and &lt;em&gt;most&lt;/em&gt; of the alien species they encounter, at least) have been largely&amp;nbsp;eliminated.&lt;/p&gt;
&lt;p&gt;Although they sometimes deal with deep topics, like the humanity or personhood of artificial intelligences, the economic reality of their universe and how it came about goes largely unexamined. There is no money, we are told (except sometimes, when there is). There is property, apparently, but whether it works the same way as property does today is not discussed. Abundant energy and the technology to conjure most of the essentials of life from thin air mean that nobody goes hungry, but whether everybody can have the opportunity to own a vinyard in France without inheriting one is a question that goes&amp;nbsp;unasked.&lt;/p&gt;
&lt;p&gt;The utopianism is usually established instead through a gentle mocking of the preoccupations and vices of present-day society, or lecturing about how humanity has moved past them. A bemused Picard takes a cigarette offered by a 20th century character on the holodeck, and coughs aggressively. Imagine smoking! Even the Ferengi, themselves caricatures of grubby, grasping capitalists, are shocked at our stupidity when they learn about tobacco. Janeway lectures the omnipotent Q about resolving conflicts with diplomacy instead of violence. An arrogant 20th century revival is revealed to be a buffoon when he demands to be allowed to speak to his lawyer. No lawyers here sir - this is the lawyerless utopia of Lionel Hutz&amp;#8217;s&amp;nbsp;nightmares.&lt;/p&gt;
&lt;p&gt;Occasionally the admonishment is more direct - such as when Sisko time travels to a ghetto in a North American city, in our near future, looks directly into the camera, and says &amp;#8220;sort your fucking shit&amp;nbsp;out&amp;#8221;.&lt;/p&gt;
&lt;p&gt;The point is that in the Star Trek future, society is better than it is today, but its material basis is vague to the point of absurdity. It&amp;#8217;s unfortunate, because that would be an interesting topic to explore, but it is what it is - light-hearted science-fantasy more concerned with the personal growth of characters than with the economic basis of their&amp;nbsp;society.&lt;/p&gt;
&lt;h2&gt;A Shallow&amp;nbsp;Dystopia&lt;/h2&gt;
&lt;p&gt;&lt;img alt="Sacre bleu!" src="https://blog.hyperlinkyourheart.com/images/picard/picard-in-disguise-cropped.jpeg" title="Sacre bleu!"&gt;&lt;/p&gt;
&lt;p&gt;Star Trek: Picard, the latest attempt at continuing the Star Trek franchise on television, is set, in contrast to its predecessors, in a fractured, broken society. The Federation has turned inward, failing to live up to its values of diplomatic and humanitarian outreach. Money has returned with a vengeance, and nobody does anything unless they&amp;#8217;re getting paid. There&amp;#8217;s a disaffected, under-appreciated working class, who apparently don&amp;#8217;t even get the same quality of replicators as other sections of society. Where characters in other series&amp;#8217; have interests and hobbies, in Picard they have vices, addictions and psychological damage. One character constantly has a fat cigar hanging out of his mouth and nobody is shocked about it because he looks, just, &lt;em&gt;so cool&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;In a show without an established universe, these aspects would be unremarkable - just a different set of assumptions about human nature and the future development of society, and with different stories to tell. In Picard, the departure from the previous utopianism is not examined, much less explained, and it is&amp;nbsp;jarring.&lt;/p&gt;
&lt;p&gt;But the above examples are trivialities compared to a problem that is at the very heart of the show. Toiling alongside the human workers are a class of sentient android slaves whose &lt;em&gt;abolition and genocide&lt;/em&gt; by the Federation serves as a major plot&amp;nbsp;motivator.&lt;/p&gt;
&lt;h2&gt;More than a&amp;nbsp;Fistful&lt;/h2&gt;
&lt;p&gt;&lt;img alt="Seriously bro?" src="https://blog.hyperlinkyourheart.com/images/picard/data-side-eye.jpg" title="Seriously bro?"&gt;&lt;/p&gt;
&lt;p&gt;These are the &amp;#8220;Datas on every starship&amp;#8221; that Maddox expresses a desire for in the &lt;span class="caps"&gt;TNG&lt;/span&gt; episode &amp;#8220;The Measure of a Man&amp;#8221;. In that episode (one of the best in the Star Trek canon), Picard defends Data&amp;#8217;s personhood and his right to self-determination. He&amp;#8217;s ready to sacrifice his career over the possibility that his society would view even &lt;em&gt;one&lt;/em&gt; android as property. In Picard, the same character disagrees with the ban on &amp;#8220;synthetics&amp;#8221;, but hardly comments on their genocide, or their previous condition of slavery. He quits Starfleet over their failure to provide humanitarian aid to the Romulans, not over the fact that the Federation had somehow come to rely on slave labour. Apparently, he would be perfectly happy to return to a situation that he fought to prevent when he was Captain of the&amp;nbsp;Enterprise.&lt;/p&gt;
&lt;p&gt;It&amp;#8217;s a disappointing missed opportunity. It seems to me that the show wanted to say something about the socio-political situation in America today, but utterly fails to understand that situation, the shows that came before it, or the character of&amp;nbsp;Picard.&lt;/p&gt;
&lt;p&gt;If it really wanted to portray the dissolution of a utopia, a crumbling society betraying its ideals, it could have done so by clarifying the nature of the Federation economy, and provided some systemic explanation for the introduction of slave labour, money, and inequality, where those things did not exist before, or at least some believable political force pushing for those things. Picard could have been cast in the role of defending the fundamental rights of the Androids from a society that is determined to exploit them, as he has many times in the past. Perhaps he could even have taken on the new role of defending the rights of human labourers, whatever the reason that they&amp;#8217;re suddenly being disenfranchised. It could have been a great opportunity to introduce some economic depth to the Star Trek universe that has long been&amp;nbsp;lacking.&lt;/p&gt;
&lt;p&gt;Instead, we get a complete mess where exploitation is ignored, the dissolution of Federation society is apparently due to infiltration by an inherently sinister other, and the androids are the ultimate villain for not being sympathetic to their oppressors or being understanding about the genocide of their&amp;nbsp;race.&lt;/p&gt;</content><category term="TV"></category><category term="star-trek"></category><category term="picard"></category><category term="notaneconomist"></category></entry><entry><title>Observatory</title><link href="https://blog.hyperlinkyourheart.com/observatory.html" rel="alternate"></link><published>2020-07-02T18:48:00+02:00</published><updated>2021-05-30T17:25:00+02:00</updated><author><name>Kevin Houlihan</name></author><id>tag:blog.hyperlinkyourheart.com,2020-07-02:/observatory.html</id><summary type="html">&lt;p&gt;An animation for Asteroid day&amp;nbsp;2020.&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;a href="https://www.redbubble.com/shop/ap/51580357"&gt;&lt;img alt="Observatory" src="https://blog.hyperlinkyourheart.com/images/observatory/ObservatoryComplete_x1_Optimised.gif" title="Observatory"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;An animation I made for &lt;a href="https://en.wikipedia.org/wiki/Asteroid_Day"&gt;Asteroid day&lt;/a&gt; to continue my space-themed art streak! Thanks to my friend &lt;a href="https://moer.tel/"&gt;Stefanie&lt;/a&gt; for the suggestion! Please click that link and check out her absolutely stunning art, you won&amp;#8217;t regret&amp;nbsp;it.&lt;/p&gt;
&lt;h2&gt;Prints&lt;/h2&gt;
&lt;p&gt;Prints are available through &lt;a href="https://www.redbubble.com/shop/ap/51580357"&gt;RedBubble&lt;/a&gt;, &lt;a href="https://www.inprnt.com/gallery/hyperlinkyourheart/pixel-art-observatory/"&gt;&lt;span class="caps"&gt;INPRNT&lt;/span&gt;&lt;/a&gt; and coming soon to&amp;nbsp;Displate.&lt;/p&gt;
&lt;h2&gt;Timelapse&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://youtu.be/Obh2zpAVn6k"&gt;&lt;img alt="Observatory Timelapse" src="https://img.youtube.com/vi/Obh2zpAVn6k/0.jpg" title="Pixel Art Timelapse - Observatory"&gt;&lt;/a&gt;&lt;/p&gt;</content><category term="Art"></category><category term="pixelart"></category><category term="space"></category><category term="animation"></category></entry><entry><title>Celestial</title><link href="https://blog.hyperlinkyourheart.com/celestial.html" rel="alternate"></link><published>2020-06-24T22:55:00+02:00</published><updated>2021-05-30T17:25:00+02:00</updated><author><name>Kevin Houlihan</name></author><id>tag:blog.hyperlinkyourheart.com,2020-06-24:/celestial.html</id><summary type="html">&lt;p&gt;Several pieces inspired by the pixel dailies theme&amp;nbsp;&amp;#8220;Celestial&amp;#8221;.&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;a href="https://www.redbubble.com/shop/ap/50838959?asc=u"&gt;&lt;img alt="Celestial 1" src="https://blog.hyperlinkyourheart.com/images/celestial/celestial1.png" title="Celestial 1"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Only a few days after the last inspiring pixel dailies prompt, another one appeared that I couldn&amp;#8217;t resist - &amp;#8220;Celestial&amp;#8221;. I had three ideas for it immediately but I only had time to do the one above on the day. I&amp;#8217;m glad I spaced them out anyway because I think I achieved a lot with the extra&amp;nbsp;time.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.redbubble.com/shop/ap/50839684?asc=u"&gt;&lt;img alt="Flyby" src="https://blog.hyperlinkyourheart.com/images/celestial/flyby.gif" title="Flyby"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The second idea I worked on was originally only a planet-rise over some mountains, but it evolved quite a bit as I worked on it until it was about spaceships racing over a peaceful alien&amp;nbsp;village.&lt;/p&gt;
&lt;p&gt;I&amp;#8217;m quite pleased with the palette and animation on this&amp;nbsp;one.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.redbubble.com/shop/ap/50840685?asc=u"&gt;&lt;img alt="Celestial 2" src="https://blog.hyperlinkyourheart.com/images/celestial/celestial2.gif" title="Celestial 2"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The final concept I only completed yesterday, a view of the night sky through some&amp;nbsp;trees.&lt;/p&gt;
&lt;h2&gt;Prints&lt;/h2&gt;
&lt;p&gt;Prints are available through &lt;a href="https://randomhumanity.redbubble.com"&gt;RedBubble&lt;/a&gt; (linked individually above), and &lt;a href="https://displate.com/hyperlinkyourheart/celestial"&gt;Displate&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Timelapses&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=bn12CoFhqLc"&gt;&lt;img alt="Celestial 1" src="https://img.youtube.com/vi/bn12CoFhqLc/0.jpg"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=xrv-JyAFfXA"&gt;&lt;img alt="Flyby" src="https://img.youtube.com/vi/xrv-JyAFfXA/0.jpg"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Available from&amp;nbsp;2020-06-25:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=Dn3sIkE6d3w"&gt;&lt;img alt="Celestial 2" src="https://img.youtube.com/vi/Dn3sIkE6d3w/0.jpg"&gt;&lt;/a&gt;&lt;/p&gt;</content><category term="Art"></category><category term="pixelart"></category><category term="pixeldailies"></category><category term="space"></category><category term="sci-fi"></category><category term="animation"></category></entry><entry><title>Cosmic Eye</title><link href="https://blog.hyperlinkyourheart.com/cosmic-eye.html" rel="alternate"></link><published>2020-06-06T21:44:00+02:00</published><updated>2021-05-30T17:25:00+02:00</updated><author><name>Kevin Houlihan</name></author><id>tag:blog.hyperlinkyourheart.com,2020-06-06:/cosmic-eye.html</id><summary type="html">&lt;p&gt;A piece I did for pixel dailies on the theme&amp;nbsp;&amp;#8220;Eye&amp;#8221;.&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;img alt="Cosmic Eye" src="https://blog.hyperlinkyourheart.com/images/cosmic-eye/SpaceEye_216_01_x1.png" title="Cosmic Eye"&gt;&lt;/p&gt;
&lt;p&gt;I got back into the pixel dailies a couple of days this week. I&amp;#8217;ve never used them as an actual daily practice, but when I see a theme I like I jump in. This day, the theme was &amp;#8220;Eye&amp;#8221;, and I &lt;em&gt;really&lt;/em&gt; like&amp;nbsp;eyes.&lt;/p&gt;
&lt;p&gt;Unfortunately I was pushed for time that day so the result is a bit rough - however, I think it gets the idea across, which is &amp;#8220;eyes which are full of stars and made of stars and also stars were&amp;nbsp;there&amp;#8221;.&lt;/p&gt;
&lt;p&gt;I started this one out with my drawing tablet, which I don&amp;#8217;t usually use for pixel art and amn&amp;#8217;t very good with. I&amp;#8217;m trying to get away from using so many rigid straight lines, and treat pixel art more like regular painting. I had to switch to the mouse towards the end for the finer details, but it&amp;#8217;s a&amp;nbsp;start.&lt;/p&gt;
&lt;h2&gt;Timelapse&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=ZwnhLEPTCOM"&gt;&lt;img alt="Cosmic Eye" src="https://img.youtube.com/vi/ZwnhLEPTCOM/0.jpg"&gt;&lt;/a&gt;&lt;/p&gt;</content><category term="Art"></category><category term="pixelart"></category><category term="pixeldailies"></category><category term="surreal"></category><category term="space"></category></entry><entry><title>Devs - Spirituality as a Service</title><link href="https://blog.hyperlinkyourheart.com/devs.html" rel="alternate"></link><published>2020-05-18T17:56:00+02:00</published><updated>2020-05-18T22:05:00+02:00</updated><author><name>Kevin Houlihan</name></author><id>tag:blog.hyperlinkyourheart.com,2020-05-18:/devs.html</id><summary type="html">&lt;p&gt;Beautiful, and perhaps appropriately predictable, but also frustrating in its conception of causality and the characters blinkered&amp;nbsp;behaviour.&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;img alt="Subtle" src="https://blog.hyperlinkyourheart.com/images/devs/forest.jpg" title="Subtle"&gt;&lt;/p&gt;
&lt;p&gt;&lt;i class="fas fa-exclamation-triangle spoiler-icon"&gt;&lt;/i&gt;&lt;span class="spoiler-text"&gt;This post contains spoilers for the &lt;span class="caps"&gt;TV&lt;/span&gt; show&amp;nbsp;&amp;#8220;Devs&amp;#8221;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;I liked &lt;a href="https://www.imdb.com/title/tt8134186/?ref_=nv_sr_srsg_0"&gt;Devs&lt;/a&gt; a lot. It looks at the quasi-religious reverence in which tech entrepreneurs are held in some quarters (most notably amongst themselves, perhaps) and asks, what if this but literally? What if these people were literally gods, or creating a&amp;nbsp;god?&lt;/p&gt;
&lt;p&gt;The plot centres on a software engineer named Lily, whose boyfriend is murdered by their boss, Forest, after he attempts to steal some code from the company they work for. The code in question is for the Devs system - a quantum simulator that extrapolates the past and future events of the entire universe from any sample of matter. Lily becomes suspicious of the circumstances of her boyfriend&amp;#8217;s death, which is made to look like a suicide, and starts to dig&amp;nbsp;around.&lt;/p&gt;
&lt;p&gt;Unfortunately much of the plot, and particularly the climax, rest on a concept that I found it hard to suspend my disbelief about (and I don&amp;#8217;t mean the premise of the Devs&amp;nbsp;system).&lt;/p&gt;
&lt;p&gt;Several of the main characters are aware of future events, up to a certain point, thanks to their quantum computer&amp;#8217;s simulations. They do not attempt to alter their behaviour in even the smallest way, even just to see if it is possible, instead slavishly repeating every word and action they&amp;#8217;ve&amp;nbsp;observed.&lt;/p&gt;
&lt;p&gt;If it were just Forest, and the lead systems designer, Katie, who acted like this, it might be understood as a consequence of blind faith, or a wilful misunderstanding of causality because reality doesn&amp;#8217;t suit their purposes. Forest is single-minded in his pursuit of this technology because he believes it can resurrect his dead daughter - Devs is his church, determinism is the creed, and anything that calls it into question is&amp;nbsp;heresy.&lt;/p&gt;
&lt;p&gt;But this notion is dispelled in a scene where a roomful of people are shown a simulation of a few seconds into the future, and mirror it exactly - apparently it is actually a feature of this universe that it is actively difficult to behave contrary to the prediction. I think the reality would be the opposite - it would actually be difficult &lt;em&gt;not&lt;/em&gt; to act differently once you were aware of future events. I think you would do so instinctively, and accidentally. It wouldn&amp;#8217;t be a violation of causality, because the simulation would also be a cause, with its own&amp;nbsp;effects.&lt;/p&gt;
&lt;p&gt;So this concept strains credibility, and works only on a allegorical level - the low-level developers are dazzled by a brief tech demo and its promises while the higher ups are simultaneously in thrall to their own hype and aware of the lies it is based on and the limits of their&amp;nbsp;knowledge.&lt;/p&gt;
&lt;p&gt;It also makes the climax of the show absurdly predictable. As soon as we hear that the simulation breaks down at a certain point, and it has something to do with Lily, we know that Lily is going to do something that contradicts the predictions of the simulation. None of the supposedly smart characters in the show demonstrate any awareness of this obvious fact, and it&amp;#8217;s frustrating. It is only redeemed because seeing the climax coming reflects the characters&amp;#8217; foreknowledge of the future, in a&amp;nbsp;way.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Lily" src="https://blog.hyperlinkyourheart.com/images/devs/lily_reflection.jpg" title="Lily doing some reflecting"&gt;&lt;/p&gt;
&lt;p&gt;Overall, it&amp;#8217;s interesting enough and well enough written that these problems are easy to look past. Some of the imagery is fantastic, such as the would-be god-developers working in a giant fractal computer floating in a vacuum, completely isolated from the world they&amp;#8217;re trying to understand. It&amp;#8217;s also a tonal masterpiece, full of haunting establishing shots, temple-like sets, and an unsettling soundtrack. Worth watching for that reason alone, to be&amp;nbsp;honest.&lt;/p&gt;</content><category term="TV"></category><category term="sci-fi"></category><category term="religion"></category></entry><entry><title>Ludum Dare 46 Results</title><link href="https://blog.hyperlinkyourheart.com/gophers-results.html" rel="alternate"></link><published>2020-05-13T22:34:00+02:00</published><updated>2020-05-13T23:58:00+02:00</updated><author><name>Kevin Houlihan</name></author><id>tag:blog.hyperlinkyourheart.com,2020-05-13:/gophers-results.html</id><summary type="html">&lt;p&gt;Results for Ludum Dare 46 are out, and I did pretty&amp;nbsp;well!&lt;/p&gt;</summary><content type="html">&lt;p&gt;The Ludum Dare 46 results were published yesterday, and &lt;a href="https://hyperlinkyourheart.itch.io/gophers"&gt;my game&lt;/a&gt; did quite well, placing 109th overall and 14th in the &amp;#8220;Mood&amp;#8221; category, as well as 120th and 121st in graphics and audio respectively. In the largest ever Ludum Dare, those are pretty decent placings I think, despite not breaking the top&amp;nbsp;100.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style="text-align: left;"&gt;Category&lt;/th&gt;
&lt;th style="text-align: right;"&gt;Rating&lt;/th&gt;
&lt;th style="text-align: right;"&gt;Placing&lt;/th&gt;
&lt;th style="text-align: right;"&gt;Percentile&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;Overall&lt;/td&gt;
&lt;td style="text-align: right;"&gt;4.136&lt;/td&gt;
&lt;td style="text-align: right;"&gt;109&lt;/td&gt;
&lt;td style="text-align: right;"&gt;96th&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;Fun&lt;/td&gt;
&lt;td style="text-align: right;"&gt;3.523&lt;/td&gt;
&lt;td style="text-align: right;"&gt;819&lt;/td&gt;
&lt;td style="text-align: right;"&gt;77th&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;Theme&lt;/td&gt;
&lt;td style="text-align: right;"&gt;4.14&lt;/td&gt;
&lt;td style="text-align: right;"&gt;279&lt;/td&gt;
&lt;td style="text-align: right;"&gt;92nd&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;Innovation&lt;/td&gt;
&lt;td style="text-align: right;"&gt;3.86&lt;/td&gt;
&lt;td style="text-align: right;"&gt;247&lt;/td&gt;
&lt;td style="text-align: right;"&gt;93rd&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;Humor&lt;/td&gt;
&lt;td style="text-align: right;"&gt;3.656&lt;/td&gt;
&lt;td style="text-align: right;"&gt;365&lt;/td&gt;
&lt;td style="text-align: right;"&gt;89th&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;Graphics&lt;/td&gt;
&lt;td style="text-align: right;"&gt;4.477&lt;/td&gt;
&lt;td style="text-align: right;"&gt;120&lt;/td&gt;
&lt;td style="text-align: right;"&gt;96th&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;Audio&lt;/td&gt;
&lt;td style="text-align: right;"&gt;4.102&lt;/td&gt;
&lt;td style="text-align: right;"&gt;121&lt;/td&gt;
&lt;td style="text-align: right;"&gt;96th&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;Mood&lt;/td&gt;
&lt;td style="text-align: right;"&gt;4.523&lt;/td&gt;
&lt;td style="text-align: right;"&gt;14&lt;/td&gt;
&lt;td style="text-align: right;"&gt;99th&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;Graphs&lt;/h2&gt;
&lt;p&gt;I always feel that the real competition in the Ludum Dare is against myself - just trying to do a little bit better and learn a bit more each time. As such, here&amp;#8217;s some indication of my &lt;span class="caps"&gt;LD&lt;/span&gt; result trends over the&amp;nbsp;years.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Ratings Graph" src="https://blog.hyperlinkyourheart.com/images/gophers-results/ratings_ogm.png"&gt;
&lt;img alt="Placings Graph" src="https://blog.hyperlinkyourheart.com/images/gophers-results/placings_ogm.png"&gt;
&lt;img alt="Percentiles Graph" src="https://blog.hyperlinkyourheart.com/images/gophers-results/percentiles_ogm.png"&gt;&lt;/p&gt;
&lt;p&gt;Nice upward trends! Note that I was only responsible for the art for &amp;#8220;Claustrophobia&amp;#8221; and &amp;#8220;Rattendorf&amp;#8221;, so I can only take partial credit for the overall and mood ratings of&amp;nbsp;those.&lt;/p&gt;
&lt;p&gt;The real learning experience this time around was on the audio. I&amp;#8217;ve only done the audio for six of the nine Ludum Dares I&amp;#8217;ve entered, so I left it out of the graphs&amp;nbsp;above.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Ratings Graph" src="https://blog.hyperlinkyourheart.com/images/gophers-results/ratings_audio.png"&gt;&lt;/p&gt;
&lt;p&gt;Looks like I really cranked it up a notch this time after coasting for a long while.&amp;nbsp;Nice.&lt;/p&gt;
&lt;h2&gt;Moar&amp;nbsp;Gophers&lt;/h2&gt;
&lt;p&gt;I haven&amp;#8217;t decided yet if I&amp;#8217;m going to take the game further. I quite like the concept and I certainly have some ideas for it. I&amp;#8217;ll probably finish off my &lt;a href="https://github.com/khoulihan/gopher-render"&gt;gopher renderer&lt;/a&gt; and phlog generator before I decide, and then I can do a &lt;em&gt;devphlog&lt;/em&gt; for it&amp;nbsp;:D&lt;/p&gt;
&lt;p&gt;You can still play the &lt;a href="https://hyperlinkyourheart.itch.io/gophers"&gt;jam version&lt;/a&gt; for now, if you missed&amp;nbsp;it.&lt;/p&gt;</content><category term="Game Development"></category><category term="ludum-dare"></category><category term="gopher"></category><category term="sci-fi"></category><category term="music"></category><category term="pixelart"></category><category term="godot"></category></entry><entry><title>Gophers</title><link href="https://blog.hyperlinkyourheart.com/gophers-post-mortem.html" rel="alternate"></link><published>2020-04-25T17:24:00+02:00</published><updated>2021-05-30T17:25:00+02:00</updated><author><name>Kevin Houlihan</name></author><id>tag:blog.hyperlinkyourheart.com,2020-04-25:/gophers-post-mortem.html</id><summary type="html">&lt;p&gt;A post-mortem of my Ludum Dare 46 entry, Gophers - a short adventure game about keeping a gopher network alive after some sort of nuclear event has destroyed&amp;nbsp;civilisation.&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;a href="https://hyperlinkyourheart.itch.io/gophers"&gt;&lt;img alt="Overlooking the city" src="https://blog.hyperlinkyourheart.com/images/gophers-post-mortem/Gophers_tower.jpg"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://hyperlinkyourheart.itch.io/gophers" title="Gophers"&gt;Gophers&lt;/a&gt; is my entry for &lt;a href="https://ldjam.com/events/ludum-dare/46" title="Ludum Dare 46"&gt;Ludum Dare 46&lt;/a&gt;, the most recent of the bi-annual Ludum Dare game jams. It is a short adventure game about maintaining a &lt;a href="https://en.wikipedia.org/wiki/Gopher_(protocol)" title="Gopher Protocol"&gt;gopher&lt;/a&gt; network in a post-apocalyptic&amp;nbsp;world.&lt;/p&gt;
&lt;p&gt;The basic concept is one I&amp;#8217;ve been kicking around for a while as a sort of casual &lt;span class="caps"&gt;RPG&lt;/span&gt;/survival game about maintaining computer networks on scavenged technology, so it came to mind immediately when I saw the theme (&amp;#8220;Keep it&amp;nbsp;alive&amp;#8221;).&lt;/p&gt;
&lt;p&gt;I&amp;#8217;ve been really interested lately in gopher and other low-overhead technologies, and what the internet would look like if the industries that sustain it collapsed. I&amp;#8217;d previously envisioned a relatively cheerful solarpunk game about connecting distant sustainable communities, but I think it took on a much darker tone because of recent&amp;nbsp;events.&lt;/p&gt;
&lt;h2&gt;Art&lt;/h2&gt;
&lt;p&gt;I did all the art in &lt;a href="https://www.pyxeledit.com/" title="Pyxel Edit"&gt;Pyxel Edit&lt;/a&gt; as usual. My goal was to keep everything abstract and as high-contrast and readable as possible while still allowing for a nice parallax cityscape. I started with a mock-up of the exterior scene, and then essentially flipped the background and foreground colours from that for the bunker scene. I only used 7 colours in the&amp;nbsp;end.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Bunker Scene" src="https://blog.hyperlinkyourheart.com/images/gophers-post-mortem/BunkerScreenie.jpg"&gt;&lt;/p&gt;
&lt;p&gt;I put together a timelapse of the art so you can see the whole&amp;nbsp;process:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=0jPLMCfSE0w"&gt;&lt;img alt="Gophers" src="https://img.youtube.com/vi/0jPLMCfSE0w/0.jpg"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;Code&lt;/h2&gt;
&lt;p&gt;The only reason why I considered this a viable idea was because I had previously developed a &lt;a href="https://github.com/khoulihan/godot-cutscene-graph" title="Cutscene Graph Editor"&gt;cutscene graph editor plugin&lt;/a&gt; for &lt;a href="https://godotengine.org/" title="The game engine you waited for."&gt;Godot&lt;/a&gt;. It was untested in any game but I thought it would give me enough of a leg up that I would have time for the art and&amp;nbsp;writing.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/khoulihan/godot-cutscene-graph" title="Cutscene Graph Editor"&gt;&lt;img alt="Graph editor" src="https://blog.hyperlinkyourheart.com/images/gophers-post-mortem/graph.jpg"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;So in effect, the &amp;#8220;gopher network&amp;#8221; in the game is actually a dialogue&amp;nbsp;tree!&lt;/p&gt;
&lt;p&gt;Actually using the editor in a game did reveal some issues with it, but nothing significant enough to prevent me from finishing - and now I have some ideas on what needs work before I use it for another&amp;nbsp;game!&lt;/p&gt;
&lt;p&gt;I also took some code from a &lt;a href="https://hyperlinkyourheart.itch.io/people-poker" title="People Poker"&gt;previous game of mine&lt;/a&gt; for doing the menus and dealing with the settings. Every bit helps when you&amp;#8217;re entering the jam&amp;nbsp;solo.&lt;/p&gt;
&lt;p&gt;One thing that really came together for me in this jam was using coroutines to manage sequences of events. I&amp;#8217;ve always struggled to wrap my head around them previously for some reason, and would clumsily hook up signal handlers for every step. Using the &lt;code&gt;yield&lt;/code&gt; statement in &lt;a href="https://godotengine.org/" title="The game engine you waited for."&gt;Godot&lt;/a&gt; made handling interactions much easier and quicker to&amp;nbsp;write.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;func&lt;/span&gt; &lt;span class="n"&gt;_on_Terminal_clicked&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;walk_target&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;face_direction&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;_player&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set_destination&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;walk_target&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;yield&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_player&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;arrived_at_destination&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;_player&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;face&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;face_direction&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;GameController&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set_spawn_location&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;bunker&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;terminal&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;GameController&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set_spawn_direction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;bunker&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;right&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;FadeMask&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fade_in&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;yield&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;FadeMask&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;fade_in_complete&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# Switch to the browser scene&lt;/span&gt;
    &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_tree&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;change_scene&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;res://browser/Browser.tscn&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;FadeMask&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fade_out&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;yield&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;FadeMask&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;fade_out_complete&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Sound&amp;nbsp;Effects&lt;/h2&gt;
&lt;p&gt;The most exciting part of working on this game, for me, was doing the sound effects. I bought a fancy mic a while back (a &lt;a href="https://www.rode.com/microphones/nt-usb" title="Røde NT-USB"&gt;Røde &lt;span class="caps"&gt;NT&lt;/span&gt;-&lt;span class="caps"&gt;USB&lt;/span&gt;&lt;/a&gt;) to do foley &lt;span class="caps"&gt;SFX&lt;/span&gt; rather than my usual &lt;span class="caps"&gt;SFXR&lt;/span&gt; beeps and boops, but this was the first chance I&amp;#8217;ve had to try it&amp;nbsp;out.&lt;/p&gt;
&lt;p&gt;&lt;img alt="My foley kit, or part of it at least" src="https://blog.hyperlinkyourheart.com/images/gophers-post-mortem/foley-kit.jpg"&gt;&lt;/p&gt;
&lt;p&gt;For the Geiger counter sounds I ran my finger over the teeth of a comb. For the bunker door, I rubbed a hammer and a spanner together in various ways. For the dripping sound in the bunker, I just used an eyedropper to drip drops into a glass of water. The footsteps are real footsteps that I recorded, and the cloth sounds when you&amp;#8217;re walking around the exterior are me crinkling a vinyl jacket. It was a lot of fun to record all these and I don&amp;#8217;t think I was even being all that creative. I couldn&amp;#8217;t figure out how to do buzzing or flickering sounds for the electric light within the time I had though,&amp;nbsp;unfortunately.&lt;/p&gt;
&lt;p&gt;One big problem I encountered was that my apartment is apparently incredibly noisy, as am I. It was a windy day and the shutters on my window were banging constantly, my neighbours were going about their noisy lives, oblivious, and my body stubbornly refused to go without oxygen during the recordings. Noise reduction in &lt;a href="https://www.audacityteam.org/" title="Audacity audio editor"&gt;Audacity&lt;/a&gt; helped a bit (make sure you record periods of &amp;#8220;silence&amp;#8221; to enable this), but there are definitely some extra environmental sounds in there. Thankfully I think they mostly just appear as mysterious underground reverb or get buried by other things. It&amp;#8217;s something I&amp;#8217;m definitely going to have to think about for next&amp;nbsp;time.&lt;/p&gt;
&lt;p&gt;I did a bunch of post-processing in &lt;a href="https://www.audacityteam.org/" title="Audacity audio editor"&gt;Audacity&lt;/a&gt; to pick the best bits out of the recordings, and make things sound better. I had to reduce the pitch on the bunker door sound to make it sound heavier, for&amp;nbsp;example.&lt;/p&gt;
&lt;h2&gt;Music&lt;/h2&gt;
&lt;p&gt;I was so proud of the sound effects that I almost wasn&amp;#8217;t going to do any music, but I&amp;#8217;m glad I did. I got to it in the last few hours of the jam, so I had to keep it very simple. It&amp;#8217;s mostly just the notes of a Dmin7 chord played in a few different arrangements on pad instruments, with some slow bass drums coming in and out. The title screen music layers a couple of different pads as well as a Rhodes doing sus4 arpeggios from each note of the&amp;nbsp;chord.&lt;/p&gt;
&lt;p&gt;I put everything together in &lt;a href="https://lmms.io/" title="LMMS"&gt;&lt;span class="caps"&gt;LMMS&lt;/span&gt;&lt;/a&gt;. I spent a good chunk of time experimenting with different instruments so even though it&amp;#8217;s really minimalistic it still took a&amp;nbsp;while!&lt;/p&gt;
&lt;h2&gt;Abandoned&amp;nbsp;Ideas&lt;/h2&gt;
&lt;p&gt;I had planned several other game elements, including the protagonist saying things to himself (or the player), and another type of interaction involving connecting cables and swapping out computer&amp;nbsp;components.&lt;/p&gt;
&lt;p&gt;A full game would probably have more complex survival elements instead of a simple timer, and would see you having to scavenge in the environment for computer equipment and other&amp;nbsp;supplies.&lt;/p&gt;
&lt;p&gt;We&amp;#8217;ll see if anything like that comes to fruition in the&amp;nbsp;future!&lt;/p&gt;</content><category term="Game Development"></category><category term="ludum-dare"></category><category term="gopher"></category><category term="sci-fi"></category><category term="music"></category><category term="pixelart"></category><category term="godot"></category></entry><entry><title>For All Mankind</title><link href="https://blog.hyperlinkyourheart.com/for-all-mankind.html" rel="alternate"></link><published>2020-04-14T16:48:00+02:00</published><updated>2020-05-17T19:09:00+02:00</updated><author><name>Kevin Houlihan</name></author><id>tag:blog.hyperlinkyourheart.com,2020-04-14:/for-all-mankind.html</id><summary type="html">&lt;p&gt;America wins, even when it&amp;nbsp;loses.&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;a href="https://pixabay.com/vectors/basic-desolate-flag-old-russia-1299705/"&gt;&lt;img alt="Red Moon" src="https://blog.hyperlinkyourheart.com/images/for-all-mankind/soviet-flag.jpg"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;i class="fas fa-exclamation-triangle spoiler-icon"&gt;&lt;/i&gt;&lt;span class="spoiler-text"&gt;This post contains spoilers for the &lt;span class="caps"&gt;TV&lt;/span&gt; show &amp;#8220;For All&amp;nbsp;Mankind&amp;#8221;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span class="dquo"&gt;&amp;#8220;&lt;/span&gt;For All Mankind&amp;#8221; is a strange show. It reimagines the space race of the late 1960s in such a way that the &lt;span class="caps"&gt;USA&lt;/span&gt; is the underdog, with the &lt;span class="caps"&gt;USSR&lt;/span&gt; beating them to the moon by a month. While &lt;span class="caps"&gt;NASA&lt;/span&gt;&amp;#8217;s failures are compounded by the crash-landing of the Apollo 11 lander, the Soviets rack up another victory when they land the first woman on the moon. Eventually the Americans get their act together and land a woman on the moon as well, and from that point on the two superpowers are neck and neck in&amp;nbsp;space.&lt;/p&gt;
&lt;p&gt;The strange thing about this is the extent to which it reflects reality, but just displaces it in time. The &lt;span class="caps"&gt;USA&lt;/span&gt; were playing catch-up for much of the space race, with the &lt;span class="caps"&gt;USSR&lt;/span&gt; achieving all the important early milestones: first artificial satellite, first animal in orbit, first human. The moon landing has so overshadowed those achievements in the popular consciousness that it is the only conceivable starting point for an alternate history like this. By giving it to the &lt;span class="caps"&gt;USSR&lt;/span&gt;, the moon landing becomes&amp;nbsp;Sputnik.&lt;/p&gt;
&lt;p&gt;The &lt;span class="caps"&gt;USSR&lt;/span&gt; did achieve another first of particular relevance to this show: they put the first woman into orbit, in 1963. Though female cosmonauts were not a permanent feature of the Soviet space program, female astronauts were not a part of the &lt;span class="caps"&gt;US&lt;/span&gt; space program at all, and they didn&amp;#8217;t put a woman into space until 20 years&amp;nbsp;later.&lt;/p&gt;
&lt;p&gt;Interestingly, though the fictional Soviet moon landing featured an actual cosmonaut (Alexei Leonov, who conducted the first spacewalk in 1965), the female cosmonaut is not Valentina Tereshkova, the first woman in space, nor any of the women in her program, but a completely fictional character. The show has no problem giving a nod to Mercury 13 candidate Jerrie Cobb in the form of fictional Molly Cobb, but the Soviet women receive no such&amp;nbsp;acknowledgement.&lt;/p&gt;
&lt;p&gt;It&amp;#8217;s not all bad. The premise feels like it is asking us to celebrate the &lt;span class="caps"&gt;USA&lt;/span&gt; for an egalitarianism that it never possessed, but the drama doesn&amp;#8217;t necessarily reflect that. The women face opposition and scepticism as to their abilities - maybe not to the extent that they would have in reality, but it&amp;#8217;s there. Gay characters have to live their lives in secret without any attempt to pretend that it could have been otherwise. America&amp;#8217;s continued participation in the space race is unequivocally driven by militarism and suspicion. The Soviet cosmonauts even get a few humanising moments, but they are ultimately cast as a sinister&amp;nbsp;other.&lt;/p&gt;
&lt;p&gt;It is sad that even now, nearly three decades on from its collapse, the Soviet Union can only ever be condemned for its failures, never acknowledged for its accomplishments. I suppose this show goes further than most in that regard, but it maintains an unquestionably American perspective, with fictional Soviet victories serving merely to encourage America on to even greater heights. It would be nice to see something from the other side some&amp;nbsp;time.&lt;/p&gt;</content><category term="TV"></category><category term="sci-fi"></category><category term="not-a-tankie-but"></category></entry><entry><title>Embedding SVGs in Pelican</title><link href="https://blog.hyperlinkyourheart.com/embedding-svgs.html" rel="alternate"></link><published>2020-04-04T00:10:00+02:00</published><updated>2020-04-04T00:10:00+02:00</updated><author><name>Kevin Houlihan</name></author><id>tag:blog.hyperlinkyourheart.com,2020-04-04:/embedding-svgs.html</id><summary type="html">&lt;p&gt;A static alternative to Font Awesome&amp;#8217;s dynamic icon&amp;nbsp;embedding.&lt;/p&gt;</summary><content type="html">&lt;p&gt;In my &lt;a href="https://blog.hyperlinkyourheart.com/remember-blogs.html"&gt;inaugural post&lt;/a&gt; I mentioned that one problem I had encountered while designing this blog was styling the &lt;span class="caps"&gt;SVG&lt;/span&gt; icons. I had grabbed a bunch of the individual icon files from &lt;a href="https://fontawesome.com/"&gt;Font Awesome&lt;/a&gt;, but because of the way SVGs, &lt;span class="caps"&gt;CSS&lt;/span&gt; and &lt;span class="caps"&gt;HTML&lt;/span&gt; interact, I wasn&amp;#8217;t able to colour them directly using &lt;span class="caps"&gt;CSS&lt;/span&gt; &lt;code&gt;color&lt;/code&gt; or &lt;code&gt;fill&lt;/code&gt; properties, and instead had to use &lt;code&gt;filter&lt;/code&gt; properties (which I calculated using &lt;a href="https://codepen.io/sosuke/pen/Pjoqqp"&gt;this tool&lt;/a&gt;, so it wasn&amp;#8217;t too much of a&amp;nbsp;hardship).&lt;/p&gt;
&lt;p&gt;I also didn&amp;#8217;t particularly like that retrieving the icons involved numerous separate requests, nor the visible &amp;#8220;pop-in&amp;#8221; in Firefox that resulted from having them referenced as external files. The files are tiny, with the request overhead often as large or larger than the files&amp;nbsp;themselves.&lt;/p&gt;
&lt;p&gt;A further advantage that I was missing out on by not using Font Awesome as intended was that I couldn&amp;#8217;t use their handy &lt;code&gt;&amp;lt;i&amp;gt;&lt;/code&gt; tag shortcuts for specifying the icons to&amp;nbsp;use.&lt;/p&gt;
&lt;p&gt;Now, I have taken steps towards solving all of these many&amp;nbsp;problems!&lt;/p&gt;
&lt;h2&gt;Just use Font Awesome normally you&amp;nbsp;weirdo&lt;/h2&gt;
&lt;p&gt;Let&amp;#8217;s back up a sec and talk about why I didn&amp;#8217;t just use Font Awesome as intended in the first place (yes tldr; it is probably because I&amp;#8217;m a&amp;nbsp;weirdo).&lt;/p&gt;
&lt;p&gt;Font Awesome has two ways that it can work: Web Fonts + &lt;span class="caps"&gt;CSS&lt;/span&gt;, or &lt;span class="caps"&gt;SVG&lt;/span&gt; + JavaScript. The former would involve retrieving an additional &lt;span class="caps"&gt;CSS&lt;/span&gt; file or two, as well as a couple of web fonts. The web font for the solid collection alone is 79.&lt;span class="caps"&gt;4KB&lt;/span&gt; - larger than anything else on this website. The JavaScript that would be required for the other method would likely be approaching &lt;span class="caps"&gt;1MB&lt;/span&gt; in size - larger than this &lt;em&gt;entire&lt;/em&gt; website so far! I want a lean, fast-loading, low-power website, and these approaches seem entirely at odds with those&amp;nbsp;goals.&lt;/p&gt;
&lt;p&gt;It also struck me as odd to be statically generating a site, yet also having the client browser swapping in &lt;span class="caps"&gt;SVG&lt;/span&gt; images. I&amp;#8217;ve nothing against JavaScript, but clearly this is work that can be done in&amp;nbsp;advance!&lt;/p&gt;
&lt;h2&gt;Doesn&amp;#8217;t caching solve this&amp;nbsp;problem?&lt;/h2&gt;
&lt;p&gt;Well&amp;#8230; maybe? In same cases? But not&amp;nbsp;necessarily.&lt;/p&gt;
&lt;p&gt;The average size of an icon in Font Awesome&amp;#8217;s &amp;#8220;solid&amp;#8221; collection is 660B. A visitor would have to encounter over 1500 such embedded icons before downloading the JavaScript and caching it would be cheaper. The Web Fonts are much better, with caching the separate files becoming worthwhile after only 214 icons. That&amp;#8217;s about 5 views of this blog&amp;#8217;s index page, or 15 individual&amp;nbsp;posts.&lt;/p&gt;
&lt;p&gt;As such, if somebody reads 16 posts on this blog, they will have transferred more data than they would have if I&amp;#8217;d used the Font Awesome web fonts. However, if 15 people read one post each and never visit again, the embedded approach comes out way ahead. So it very much depends on the traffic profile of the site, and I don&amp;#8217;t think this site is one that people will be checking in on&amp;nbsp;daily.&lt;/p&gt;
&lt;p&gt;Embedding also offers other advantages, such as reducing initial load&amp;nbsp;times.&lt;/p&gt;
&lt;h2&gt;Solutions&lt;/h2&gt;
&lt;p&gt;My solution is a &lt;a href="https://github.com/khoulihan/pelican-embed-svg"&gt;pelican plugin&lt;/a&gt; that post-processes the generated &lt;span class="caps"&gt;HTML&lt;/span&gt; files and embeds any SVGs it finds, whether specified as &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; tags or &lt;code&gt;&amp;lt;i&amp;gt;&lt;/code&gt; tags.&lt;/p&gt;
&lt;p&gt;It also, crucially, sets the &lt;code&gt;fill&lt;/code&gt; attribute of any &lt;span class="caps"&gt;SVG&lt;/span&gt; paths to &lt;code&gt;currentColor&lt;/code&gt;, which causes the fill colour to be taken from the current &lt;span class="caps"&gt;CSS&lt;/span&gt; text&amp;nbsp;colour.&lt;/p&gt;
&lt;p&gt;Taking the plugin beyond being merely a static implementation of Font Awesome, it also supports embedding of arbitrary &lt;span class="caps"&gt;SVG&lt;/span&gt; files. This can be achieved either by using &lt;code&gt;&amp;lt;i&amp;gt;&lt;/code&gt; tags with the class &lt;code&gt;pi&lt;/code&gt; to search a custom icon set, or through &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; tags where the &lt;span class="caps"&gt;SVG&lt;/span&gt; file is referenced by &lt;span class="caps"&gt;URL&lt;/span&gt;.&lt;/p&gt;
&lt;h2&gt;Future&lt;/h2&gt;
&lt;p&gt;The plugin probably has loads of rough edges at the moment. I haven&amp;#8217;t at all tested if it supports Font Awesome&amp;#8217;s more advanced behaviour, or even investigated how those features work, so there is a lot to be done&amp;nbsp;there.&lt;/p&gt;
&lt;p&gt;I may explore an approach that would combine the advantages of static generation with the advantages of a separate, cacheable &lt;span class="caps"&gt;SVG&lt;/span&gt; file. My initial thoughts on how to approach this plugin were to combine any referenced SVGs into a single file, and then reference them in the &lt;span class="caps"&gt;HTML&lt;/span&gt; using an &lt;span class="caps"&gt;SVG&lt;/span&gt; &lt;code&gt;&amp;lt;use&amp;gt;&lt;/code&gt; tag. I need to learn a lot more about SVGs to know if that&amp;#8217;s even&amp;nbsp;feasible.&lt;/p&gt;
&lt;p&gt;I also want to try to support other icon frameworks that support a similar &lt;code&gt;&amp;lt;i&amp;gt;&lt;/code&gt; tag shortcut, such as &lt;a href="https://forkaweso.me/Fork-Awesome/"&gt;Fork Awesome&lt;/a&gt; and &lt;a href="https://friconix.com"&gt;Friconix&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In the meantime, it&amp;#8217;s serving my purposes already on this&amp;nbsp;site.&lt;/p&gt;
&lt;p&gt;&lt;i class="fas fa-thumbs-up body-icon"&gt;&lt;/i&gt; &lt;i class="fas fa-bomb body-icon"&gt;&lt;/i&gt; &lt;i class="fas fa-cat body-icon"&gt;&lt;/i&gt; &lt;i class="fas fa-leaf body-icon"&gt;&lt;/i&gt; &lt;i class="fas fa-file body-icon"&gt;&lt;/i&gt; &lt;i class="fas fa-fist-raised body-icon"&gt;&lt;/i&gt;&lt;/p&gt;</content><category term="Programming"></category><category term="python"></category><category term="blogging"></category><category term="pelican"></category></entry><entry><title>Runtime Class Modification</title><link href="https://blog.hyperlinkyourheart.com/runtime-class-modification.html" rel="alternate"></link><published>2020-03-25T17:51:00+01:00</published><updated>2020-03-25T23:12:00+01:00</updated><author><name>Kevin Houlihan</name></author><id>tag:blog.hyperlinkyourheart.com,2020-03-25:/runtime-class-modification.html</id><summary type="html">&lt;p&gt;Batteries-included package development in Python requires an unorthodox approach when targeting&amp;nbsp;microcontrollers.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Python is probably my favourite language, so I was excited some years ago when a project appeared on Kickstarter to develop a &lt;a href="http://micropython.org/" title="MicroPython"&gt;Python runtime for microcontrollers&lt;/a&gt;, and an associated microcontroller&amp;nbsp;board.&lt;/p&gt;
&lt;p&gt;However, writing Python for a microcontroller does have some constraints that aren&amp;#8217;t really a factor when writing Python for other environments. Having maybe only &lt;span class="caps"&gt;100KB&lt;/span&gt; of &lt;span class="caps"&gt;RAM&lt;/span&gt; to work with, keeping code size as low as possible is&amp;nbsp;essential.&lt;/p&gt;
&lt;p&gt;When I wrote a &lt;a href="https://github.com/khoulihan/micropython-tmp102" title="micropython-tmp102 repository"&gt;package to support the &lt;span class="caps"&gt;TI&lt;/span&gt; tmp102 temperature sensor&lt;/a&gt;, I initially included all the required functionality in a single importable class. It used &lt;span class="caps"&gt;15KB&lt;/span&gt; of &lt;span class="caps"&gt;RAM&lt;/span&gt; after import, which does leave space for other code, but since some of the functionality is mutually exclusive I knew I could probably do&amp;nbsp;better.&lt;/p&gt;
&lt;p&gt;This post is about what I ended up with and how it&amp;nbsp;works.&lt;/p&gt;
&lt;h2&gt;Importable&amp;nbsp;Features&lt;/h2&gt;
&lt;p&gt;The core functionality of the package can be leveraged by importing the &lt;code&gt;Tmp102&lt;/code&gt; class and creating an instance. This leaves the sensor in its default configuration, in which it performs a reading 4 times per second and makes the most recent available to your code on request. The details of initialising the object are explained in the &lt;a href="https://github.com/khoulihan/micropython-tmp102/blob/master/README.md"&gt;documentation&lt;/a&gt; if you actually want to use the module, so I won&amp;#8217;t go into them again&amp;nbsp;here.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;machine&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;I2C&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;tmp102&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Tmp102&lt;/span&gt;
&lt;span class="n"&gt;bus&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;I2C&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;sensor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Tmp102&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bus&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0x48&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sensor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;temperature&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;That&amp;#8217;s all well and good, but what if you want to make use of some of the more advanced features of the sensor, such as controlling the rate at which it takes readings (the &amp;#8220;conversion rate&amp;#8221;)? Such features are structured as importable modules which add the required functionality into the &lt;code&gt;Tmp102&lt;/code&gt; class. The &lt;code&gt;CONVERSION_RATE_1HZ&lt;/code&gt; constant in the example below, as well as other relevant code, are added to the class when the &lt;code&gt;conversionrate&lt;/code&gt; module is&amp;nbsp;imported.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;tmp102&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Tmp102&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;tmp102.conversionrate&lt;/span&gt;
&lt;span class="n"&gt;sensor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Tmp102&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;bus&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="mh"&gt;0x48&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;conversion_rate&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;Tmp102&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CONVERSION_RATE_1HZ&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;If you don&amp;#8217;t need to change the conversion rate in your project then the code to do so is never loaded. If you do need this or other features, all the functionality is still exposed through a single easy to use&amp;nbsp;class.&lt;/p&gt;
&lt;h2&gt;How?&lt;/h2&gt;
&lt;p&gt;The package is structured like&amp;nbsp;this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;tmp102
+-- __init__.py
+-- _tmp102.py
+-- alert.py
+-- conversionrate.py
+-- convertors.py
+-- extendedmode.py
+-- oneshot.py
+-- shutdown.py
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The base &lt;code&gt;Tmp102&lt;/code&gt; class is defined in &lt;code&gt;_tmp102.py&lt;/code&gt;, along with some private functions and&amp;nbsp;constants.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;REGISTER_TEMP&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="n"&gt;REGISTER_CONFIG&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;

&lt;span class="n"&gt;EXTENDED_MODE_BIT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mh"&gt;0x10&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;_set_bit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mask&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;mask&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;_clear_bit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mask&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;~&lt;/span&gt;&lt;span class="n"&gt;mask&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;_set_bit_for_boolean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mask&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;val&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;val&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;_set_bit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mask&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;_clear_bit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mask&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;Tmp102&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;object&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bus&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;temperature_convertor&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bus&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;bus&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;address&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;address&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;temperature_convertor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;temperature_convertor&lt;/span&gt;
        &lt;span class="c1"&gt;# The register defaults to the temperature.&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_last_write_register&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;REGISTER_TEMP&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_extended_mode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;To hide the private stuff from users of the package, the &lt;code&gt;__init__.py&lt;/code&gt; imports the &lt;code&gt;Tmp102&lt;/code&gt; class and then removes the &lt;code&gt;_tmp102&lt;/code&gt; module from the&amp;nbsp;namespace.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;tmp102._tmp102&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Tmp102&lt;/span&gt;

&lt;span class="k"&gt;del&lt;/span&gt; &lt;span class="n"&gt;_tmp102&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The interesting stuff happens in the feature sub-modules. Each feature module defines an &lt;code&gt;_extend_class&lt;/code&gt; function which modifies the &lt;code&gt;Tmp102&lt;/code&gt; class. Since importing a module runs it, this function can be called and then deleted to keep the namespace nice and clean - the module will actually be empty once imported. This pattern should be familiar to JavaScript&amp;nbsp;developers!&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;_extend_class&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="c1"&gt;# Modify Tmp102 here - Check the next code block!&lt;/span&gt;
    &lt;span class="k"&gt;pass&lt;/span&gt;

&lt;span class="n"&gt;_extend_class&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;del&lt;/span&gt; &lt;span class="n"&gt;_extend_class&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Let&amp;#8217;s take a look at the &lt;code&gt;oneshot&lt;/code&gt; module, which adds functionality to the &lt;code&gt;Tmp102&lt;/code&gt; class to allow the sensor to be polled as necessary instead of constantly performing readings - very useful if you want to save&amp;nbsp;power.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;_extend_class&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;tmp102._tmp102&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Tmp102&lt;/span&gt;
    &lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;tmp102._tmp102&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;_set_bit_for_boolean&lt;/span&gt;
    &lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;tmp102.shutdown&lt;/span&gt;

    &lt;span class="n"&gt;SHUTDOWN_BIT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mh"&gt;0x01&lt;/span&gt;
    &lt;span class="n"&gt;ONE_SHOT_BIT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mh"&gt;0x80&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;initiate_conversion&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="sd"&gt;        Initiate a one-shot conversion.&lt;/span&gt;
&lt;span class="sd"&gt;        &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
        &lt;span class="n"&gt;current_config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_get_config&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;current_config&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;SHUTDOWN_BIT&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="ne"&gt;RuntimeError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Device must be shut down to initiate one-shot conversion&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;new_config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;bytearray&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;current_config&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;new_config&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_set_bit_for_boolean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;new_config&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="n"&gt;ONE_SHOT_BIT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="kc"&gt;True&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_set_config&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;new_config&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;Tmp102&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;initiate_conversion&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;initiate_conversion&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;_conversion_ready&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;current_config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_get_config&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;current_config&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;ONE_SHOT_BIT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;ONE_SHOT_BIT&lt;/span&gt;
    &lt;span class="n"&gt;Tmp102&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;conversion_ready&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;property&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_conversion_ready&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;So what&amp;#8217;s going on here? First, the &lt;code&gt;Tmp102&lt;/code&gt; class and any required functions are imported. Since it was imported in the package&amp;#8217;s &lt;code&gt;__init__&lt;/code&gt; the class is already defined. Importing the private functions and constants in a function like this keeps them out of the global&amp;nbsp;namespace.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;tmp102._tmp102&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Tmp102&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;tmp102._tmp102&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;_set_bit_for_boolean&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;oneshot&lt;/code&gt; module depends on the functionality from the &lt;code&gt;shutdown&lt;/code&gt; module, so it is imported&amp;nbsp;next.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;tmp102.shutdown&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Next, a couple of constants are defined. Through the magic of closure, these will only be available to the methods defined in this&amp;nbsp;module.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;SHUTDOWN_BIT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mh"&gt;0x01&lt;/span&gt;
&lt;span class="n"&gt;ONE_SHOT_BIT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mh"&gt;0x80&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The rest of the function defines a method and a property which are added to the class by simply assigning them to attributes. These will be available to any instances of the class, exactly as if they were included in the class&amp;nbsp;definition.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;initiate_conversion&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="sd"&gt;    Initiate a one-shot conversion.&lt;/span&gt;
&lt;span class="sd"&gt;    &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="n"&gt;current_config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_get_config&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;current_config&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;SHUTDOWN_BIT&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="ne"&gt;RuntimeError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Device must be shut down to initiate one-shot conversion&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;new_config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;bytearray&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;current_config&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;new_config&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_set_bit_for_boolean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;new_config&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="n"&gt;ONE_SHOT_BIT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="kc"&gt;True&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_set_config&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;new_config&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;Tmp102&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;initiate_conversion&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;initiate_conversion&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;_conversion_ready&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;current_config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_get_config&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;current_config&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;ONE_SHOT_BIT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;ONE_SHOT_BIT&lt;/span&gt;
&lt;span class="n"&gt;Tmp102&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;conversion_ready&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;property&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_conversion_ready&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The other feature modules follow the same&amp;nbsp;pattern.&lt;/p&gt;
&lt;h2&gt;Savings&lt;/h2&gt;
&lt;p&gt;Importing the base &lt;code&gt;Tmp102&lt;/code&gt; class uses about 3.&lt;span class="caps"&gt;53KB&lt;/span&gt; of &lt;span class="caps"&gt;RAM&lt;/span&gt; - quite a saving if that is all you need. The feature modules vary between 0.&lt;span class="caps"&gt;8KB&lt;/span&gt; and &lt;span class="caps"&gt;4KB&lt;/span&gt;, or thereabouts. Importing them all uses 13.&lt;span class="caps"&gt;44KB&lt;/span&gt;, but it is unlikely that they would all be required in any given&amp;nbsp;application.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;I thought of this approach as &amp;#8220;monkey-patching&amp;#8221; for a long time - the last refuge of the desperate and the damned - but I&amp;#8217;m not sure that it is really, because the modifications are all being made internally to the package. It is definitely outside the norm for Python, but it achieved the goal of reducing &lt;span class="caps"&gt;RAM&lt;/span&gt; usage while maintaining a clean &lt;span class="caps"&gt;API&lt;/span&gt;.&lt;/p&gt;</content><category term="Programming"></category><category term="python"></category><category term="micropython"></category></entry><entry><title>Self-Fulfilling Prophecies</title><link href="https://blog.hyperlinkyourheart.com/prophecies.html" rel="alternate"></link><published>2020-03-22T15:43:00+01:00</published><updated>2020-03-22T15:43:00+01:00</updated><author><name>Kevin Houlihan</name></author><id>tag:blog.hyperlinkyourheart.com,2020-03-22:/prophecies.html</id><summary type="html">&lt;p&gt;Expectations of shortages lead to&amp;nbsp;shortages.&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;a href="https://pixabay.com/photos/toilet-paper-hamster-purchases-panic-4941768/"&gt;&lt;img alt="Don't Panic" src="https://blog.hyperlinkyourheart.com/images/prophecies/toilet-paper.jpg"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;We see it in every crisis - somebody posts a picture on social media of a bare shelf or a rumour goes around that the shops are running out of something (such as, to pick a good completely at random, toilet paper), and suddenly the shelves are emptying everywhere, and it seems to make sense to secure a&amp;nbsp;stockpile.&lt;/p&gt;
&lt;p&gt;It starts as an irrational fear, but it is reified by the seemingly rational self interests of individual consumers. It makes sense, on an individual level, to buy extra because everybody else is, or might be. The expectation of shortages leads to shortages, just as the expectation of economic growth helps create growth, and the fear of a crash leads to or worsens a crash, as everybody tries to get off the merry-go-round at the same&amp;nbsp;time.&lt;/p&gt;
&lt;p&gt;Market economies amplify and feed off our emotions and impulses in the face of incomplete information. We&amp;#8217;re not generally privy to the details of the stocks and supply chains of any given good. If we were, we could determine whether a perceived shortage is real and how long it might be expected to last, and act accordingly. Even better than obtaining and acting on such information individually - which could still lead to panic buying in the event of an actual shortage - would be to evaluate and respond to the situation collectively, to ensure that everybody can get a reasonable share of goods even in the event of a&amp;nbsp;shortage.&lt;/p&gt;
&lt;p&gt;Markets don&amp;#8217;t offer any mechanism for collective reasoning or action. The best a market can offer is price-gouging, where massive price increases disuade all but the most desparate until everybody comes to their senses. Thankfully, retailers in societies that haven&amp;#8217;t completely devolved into neoliberal hellscapes tend to opt for rationing instead. Nobody wants to be seen to be a profiteer by a community that they are going to want to continue to serve after the crisis has&amp;nbsp;passed.&lt;/p&gt;
&lt;p&gt;It&amp;#8217;s unfortunate that we have to be reliant on the reputational concerns of retailers to ensure the provision of essential goods in a crisis. The expectation of shortages leads to shortages, but somehow the certainty of occasional crises doesn&amp;#8217;t lead to distributed production, resilient supply chains, or emergency stockpiles. Our economy&amp;#8217;s blinkered focus on short-term profits and fetishisation of &amp;#8220;efficiency&amp;#8221; doesn&amp;#8217;t allow for this kind of&amp;nbsp;thinking.&lt;/p&gt;</content><category term="Economics"></category><category term="panic"></category><category term="coronavirus"></category><category term="notaneconomist"></category></entry><entry><title>Catnip</title><link href="https://blog.hyperlinkyourheart.com/catnip.html" rel="alternate"></link><published>2020-01-23T15:52:00+01:00</published><updated>2020-01-23T16:24:00+01:00</updated><author><name>Kevin Houlihan</name></author><id>tag:blog.hyperlinkyourheart.com,2020-01-23:/catnip.html</id><summary type="html">&lt;p&gt;An animation of a cat hunting pixels on a 70&amp;#8217;s styled&amp;nbsp;computer.&lt;/p&gt;</summary><content type="html">&lt;p&gt;I put together an animation over the last few weeks of a cat trying to catch &lt;a href="https://en.wikipedia.org/wiki/Glider_(Conway%27s_Life)"&gt;gliders&lt;/a&gt; on a 70&amp;#8217;s styled computer. Check it out on&amp;nbsp;YouTube:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=2PqFIOkhUSk"&gt;&lt;img alt="Catnip" src="https://img.youtube.com/vi/2PqFIOkhUSk/0.jpg"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I actually only set out to draw the computer, and I&amp;#8217;m not sure at what point the cat entered the picture, but I&amp;#8217;m glad it did! My goal wasn&amp;#8217;t to depict any specific vintage computer, but to create a somewhat implausible one from imagination. I did look at a bunch of references for ideas on what to include, mostly from &lt;a href="http://oldcomputers.net/"&gt;oldcomputers.net&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;Music&lt;/h2&gt;
&lt;p&gt;I had originally planned to create a patch in VCVRack to accompany the animation, but I struggled to create something that felt right for the animation. Several attempts to create something in &lt;span class="caps"&gt;LMMS&lt;/span&gt; also failed. I ended up putting something together in &lt;a href="https://beepbox.co/#8n51s6k0l00e0zt2mm0a7g0zj07i0r1o321440T1v2L4u25q1d5f7y0z8C0c0A5F4B0V1Q000dPc696E0018T1v1L4u63q1d5f7y1z7C1c0A1F1B4V1Q50b0Pea3bE0181T1v3L4u57q1d5f4y4z2C1c0A0F9B4V8Q0040P9900E0111T1v3L4ue1q3d7f7y2zbC0c0A0F0B7V1Q0000Pe600E0911T0v1L4ua7q3d6f8y4z1C0w0c1h0T3v1L4uf7q1d5f7y3z6C1SZIztrsrzrqiiiiibhkki4N8l0000018j4xkg4S00000g4x0j4xci4N0i41ci4Q00hkki4N8j4xci4N8j4xd5hm00000000004x8i4x8i4x800000000000000000018j4xci4M00000h4x4i000h4x4i4h800000p23GKrF-9isT7URQVH-p4U7CpKkVJvGBbXHKtSRnrARlLmrnZFdv_jvonQYPIyYLLKKYXZDvBCR-9jnUtc3onQYMcU_0rrupCh1KrhZGxqJQmVKfZ-PLMFKf-jTY3V2CnQUaD8S0sxdvVOILbOWrnUbYDcdvonVCmq_se_aKU_W-U_wuIXz_HW_TW_iL0arF-EvcBC8bwkRUwnFB0zhjhy9BV9EZELOjhOs25E5PYAmwk0"&gt;beepbox&lt;/a&gt;.&lt;/p&gt;</content><category term="Art"></category><category term="pixelart"></category><category term="animation"></category><category term="music"></category></entry><entry><title>Bad Idioms</title><link href="https://blog.hyperlinkyourheart.com/bad-idioms.html" rel="alternate"></link><published>2020-01-10T14:11:00+01:00</published><updated>2020-03-25T23:20:00+01:00</updated><author><name>Kevin Houlihan</name></author><id>tag:blog.hyperlinkyourheart.com,2020-01-10:/bad-idioms.html</id><summary type="html">&lt;p&gt;A brief investigation of Python&amp;#8217;s &lt;span class="caps"&gt;EAFP&lt;/span&gt; idiom applied to&amp;nbsp;C#.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Human languages are full to the brim with idioms - figurative ways of saying things that native speakers trot out without even thinking about them. Often, when translated literally into another language, the result is utter nonsense. For example, the phrase &amp;#8220;tomar el pelo&amp;#8221; in Spanish translates literally to English as &amp;#8220;to take the hair&amp;#8221;, but the idiomatic way to say the same thing in English would be &amp;#8220;to pull (someone&amp;#8217;s) leg&amp;#8221;. The same thing is roughly true of programming languages, with different languages having their own idiomatic or expected ways of achieving the same&amp;nbsp;ends.&lt;/p&gt;
&lt;p&gt;I recently made the mistake, after a period of writing Python code, of applying one of Python&amp;#8217;s idioms to C#. The task at hand was to check if a dictionary of lists already contained a particular key, and if not, add a new list for that key. The C# way to do this would probably be to check for the existence of the key first, then decide what to do - or even better, use the &lt;code&gt;TryGetValue&lt;/code&gt; method of the dictionary to assign the value to a variable. This is known as &amp;#8220;Look Before You&amp;nbsp;Leap&amp;#8221;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;object&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ContainsKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;objectType&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;objectType&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;object&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;objectType&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;object&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TryGetValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;objectType&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;out&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;object&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;objectType&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;But instead of doing either of those things, I applied a more pythonic idiom - that of &amp;#8220;Easier to Ask Permission than Forgiveness&amp;#8221; - and just tried retrieving the value, and catching the &lt;code&gt;KeyNotFoundException&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;object&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;try&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;objectType&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;KeyNotFoundException&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;object&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;objectType&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This turned an operation that should have taken milliseconds into one that was taking seconds, introducing a perceptible delay into my&amp;nbsp;application.&lt;/p&gt;
&lt;p&gt;Curious to know to exactly what extent performance differed between the above choices, and whether &lt;span class="caps"&gt;EAFP&lt;/span&gt; really would have been the better choice in Python, I decided to throw together some benchmark&amp;nbsp;tests.&lt;/p&gt;
&lt;h2&gt;Python&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;timeit&lt;/span&gt;

&lt;span class="n"&gt;setup&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="s2"&gt;d = {&lt;/span&gt;
&lt;span class="s2"&gt;    &amp;#39;a&amp;#39;: [1, 2, 3,],&lt;/span&gt;
&lt;span class="s2"&gt;    &amp;#39;b&amp;#39;: [4, 5, 6,],&lt;/span&gt;
&lt;span class="s2"&gt;    &amp;#39;c&amp;#39;: [7, 8, 9,],&lt;/span&gt;
&lt;span class="s2"&gt;}&lt;/span&gt;
&lt;span class="s2"&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;

&lt;span class="n"&gt;test_except&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="s2"&gt;try:&lt;/span&gt;
&lt;span class="s2"&gt;    v = d[&amp;#39;d&amp;#39;]&lt;/span&gt;
&lt;span class="s2"&gt;except KeyError:&lt;/span&gt;
&lt;span class="s2"&gt;    v = []&lt;/span&gt;
&lt;span class="s2"&gt;    d[&amp;#39;d&amp;#39;] = v&lt;/span&gt;

&lt;span class="s2"&gt;del d[&amp;#39;d&amp;#39;]&lt;/span&gt;
&lt;span class="s2"&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;

&lt;span class="n"&gt;test_check&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="s2"&gt;if &amp;#39;d&amp;#39; in d:&lt;/span&gt;
&lt;span class="s2"&gt;    v = d[&amp;#39;d&amp;#39;]&lt;/span&gt;
&lt;span class="s2"&gt;else:&lt;/span&gt;
&lt;span class="s2"&gt;    v = []&lt;/span&gt;
&lt;span class="s2"&gt;    d[&amp;#39;d&amp;#39;] = v&lt;/span&gt;

&lt;span class="s2"&gt;del d[&amp;#39;d&amp;#39;]&lt;/span&gt;
&lt;span class="s2"&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;

&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;timeit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;timeit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;setup&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;setup&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stmt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;test_except&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;number&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1000000&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;timeit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;timeit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;setup&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;setup&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stmt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;test_check&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;number&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1000000&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This gave results of 0.46 seconds for a million &lt;span class="caps"&gt;EAFP&lt;/span&gt; operations, and about 0.08 seconds for a million &lt;span class="caps"&gt;LBYL&lt;/span&gt; operations, with everything else, I hope, being equal between the two tests. If the new key is not deleted every time (so that only the first check fails), the &lt;span class="caps"&gt;EAFP&lt;/span&gt; operation becomes marginally faster than the alternative (0.026 vs 0.037 seconds) on most&amp;nbsp;runs.&lt;/p&gt;
&lt;h2&gt;C#&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;Dictionary&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;dict&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Dictionary&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;a&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;b&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;c&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="n"&gt;DateTime&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;exceptStart&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;DateTime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UtcNow&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;d&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;KeyNotFoundException&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;d&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;d&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;TimeSpan&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;exceptResult&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;DateTime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UtcNow&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;exceptStart&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="n"&gt;DateTime&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;tryGetStart&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;DateTime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UtcNow&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1000000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TryGetValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;d&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;out&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;d&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;d&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;TimeSpan&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;tryGetResult&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;DateTime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UtcNow&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;tryGetStart&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="n"&gt;DateTime&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;checkStart&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;DateTime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UtcNow&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1000000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ContainsKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;d&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;d&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;d&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;d&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;TimeSpan&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;checkResult&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;DateTime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UtcNow&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;checkStart&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Except: {0}&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;exceptResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TotalSeconds&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;TryGet: {0:f10}&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;tryGetResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TotalSeconds&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Check: {0:f10}&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;checkResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TotalSeconds&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ReadKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Note that the &lt;span class="caps"&gt;EAFP&lt;/span&gt; test here is only performed a thousand times - because even running it that many times takes around 15 &lt;em&gt;entire&lt;/em&gt; seconds! The two &lt;span class="caps"&gt;LBYL&lt;/span&gt; tests are nothing in comparison, executing a million times in around 0.05 seconds. This is a much bigger difference than I would have&amp;nbsp;expected.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;The performance of a single operation like this doesn&amp;#8217;t necessarily say a lot about the real-world performance of any given application, but I think it is probably best to stick to the idioms of the language you&amp;#8217;re working in - and in C#, that means only throwing exceptions in exceptional circumstances. In Python, there may be circumstances where it would be better to &amp;#8220;Look Before You Leap&amp;#8221; as well, but the difference in performance is probably not large enough to matter in most&amp;nbsp;cases.&lt;/p&gt;</content><category term="Programming"></category><category term="python"></category><category term="c-sharp"></category></entry><entry><title>Remember Blogs?</title><link href="https://blog.hyperlinkyourheart.com/remember-blogs.html" rel="alternate"></link><published>2020-01-02T20:05:00+01:00</published><updated>2020-01-02T20:05:00+01:00</updated><author><name>Kevin Houlihan</name></author><id>tag:blog.hyperlinkyourheart.com,2020-01-02:/remember-blogs.html</id><summary type="html">&lt;p&gt;The how and the why of this very blog right&amp;nbsp;here.&lt;/p&gt;</summary><content type="html">&lt;p&gt;I&amp;#8217;ve read a lot of articles recently (&lt;a href="https://omarabid.com/the-modern-web"&gt;here&amp;#8217;s one&lt;/a&gt;) lamenting the state of the web. Once distributed, egalitarian, ungovernable, and fast, now centralised, intentionally manipulative, and bloated both technically and conceptually. Even when you manage to fight your way through the popups demanding your attention or personal information, often what is underneath is not worth the effort - more likely a vehicle for advertising than for&amp;nbsp;insight.&lt;/p&gt;
&lt;p&gt;It&amp;#8217;s also incredibly power-hungry. It&amp;#8217;s hard to tie down an exact figure for exactly &lt;em&gt;how&lt;/em&gt; power-hungry, but the internet as a whole &lt;a href="https://newrepublic.com/article/155993/can-internet-survive-climate-change"&gt;could account for up to 10% of global energy use&lt;/a&gt;. A good chunk of that is streaming video and music, which is a topic for another day, but of the power consumed in serving the web, some of it is related to actual valuable content that people want to see, and some of it is related to the trends described above. The latter is waste. At least bloated JavaScript and &lt;span class="caps"&gt;CSS&lt;/span&gt; frameworks can be cached, but advertising has to be constantly served&amp;nbsp;anew.&lt;/p&gt;
&lt;p&gt;So, anyway, all this to say&amp;#8230; I&amp;#8217;ve decided to start a&amp;nbsp;blog.&lt;/p&gt;
&lt;h2&gt;The&amp;nbsp;Tech&lt;/h2&gt;
&lt;p&gt;My technical goals for this website are for it to&amp;nbsp;be&amp;#8230;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Lightweight &lt;span class="amp"&gt;&amp;amp;&lt;/span&gt; fast to load&lt;/strong&gt; - I set up a WordPress site recently, on the best hosting I can afford. It is not lightweight or fast to&amp;nbsp;load.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Content focused&lt;/strong&gt; - Read one thing or read them all, but I&amp;#8217;m sure you can only read one article at a&amp;nbsp;time.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Nice to look at&lt;/strong&gt; - Apparently it &lt;a href="https://perfectmotherfuckingwebsite.com/"&gt;doesn&amp;#8217;t take much&lt;/a&gt;. Also going for consistent &lt;em&gt;branding&lt;/em&gt; between all my sites and&amp;nbsp;profiles.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Responsive&lt;/strong&gt; - Readable on phones as well as&amp;nbsp;desktops!&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Easy to deploy&lt;/strong&gt; - I don&amp;#8217;t have time to configure and maintain a teetering stack of back-end technology, and if I have to move to different hosting at some point, I want it to be a simple&amp;nbsp;task.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Easy to update&lt;/strong&gt; - If writing posts is a chore, I won&amp;#8217;t ever do&amp;nbsp;it.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Hackable&lt;/strong&gt; - Created using technologies that I&amp;#8217;m somewhat familiar with, so that it is feasible for me to modify or extend if I want/need&amp;nbsp;to.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I decided almost immediately that a statically-generated site was going to be the best way to achieve most of those goals. I&amp;#8217;m a big fan of Python, so although &lt;em&gt;hackability&lt;/em&gt; could be achieved by a JavaScript or C# based generator, I checked out the Python ones first, and found plenty of viable options. I settled on &lt;a href="https://blog.getpelican.com/"&gt;Pelican&lt;/a&gt; because&amp;nbsp;it&amp;#8217;s&amp;#8230;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Popular&lt;/strong&gt; - It seems to be one of the more popular Python&amp;nbsp;generators.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Blog-oriented&lt;/strong&gt; - Some generators are geared towards documentation or are intended as replacements for content management systems, but that&amp;#8217;s not what I&amp;#8217;m&amp;nbsp;doing.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Supports Markdown&lt;/strong&gt; - I&amp;#8217;m sure reST is fine, but I already have to use Markdown elsewhere so I&amp;#8217;d rather stick with&amp;nbsp;that.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Easy to update&lt;/strong&gt; - Just create a new Markdown file and run a command to&amp;nbsp;rebuild.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Extensible&lt;/strong&gt; - It includes a plugin system to modify the&amp;nbsp;output.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I also decided to hand-craft my own theme, and to avoid a &lt;span class="caps"&gt;CSS&lt;/span&gt; framework. I love the look of Bootstrap, and how quick it is to get started with, but it&amp;#8217;s over 200kb and a lot of that is undoubtedly unnecessary for my needs. The spirit of the exercise is bare-bones and &lt;span class="caps"&gt;DIY&lt;/span&gt;!&lt;/p&gt;
&lt;h3&gt;The&amp;nbsp;Theme&lt;/h3&gt;
&lt;p&gt;The first step in hand-crafting a theme was&amp;#8230; to find an existing theme to copy! &lt;a href="https://github.com/arulrajnet/attila"&gt;Atilla&lt;/a&gt; was the closest to the style I was after, so I took a copy of that and gutted it of &lt;span class="caps"&gt;CSS&lt;/span&gt; and JavaScript and other elements that didn&amp;#8217;t meet my needs. Then I started building the &lt;span class="caps"&gt;CSS&lt;/span&gt; back up while trying to keep it as minimal as possible. It may not implement every feature supported by Pelican, but you can find it &lt;a href="https://github.com/khoulihan/hyh-blog/tree/master/themes/hyper"&gt;on my Github&lt;/a&gt; if it seems like something you could adapt for your own&amp;nbsp;needs.&lt;/p&gt;
&lt;p&gt;One departure that I made from the standard Pelican configuration was to have the social media links be taken from a collection of tuples with three elements, so that I could specify both an icon and a title to&amp;nbsp;use.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# Custom social list that includes icons&lt;/span&gt;
&lt;span class="n"&gt;SOCIAL_ICONS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Twitter&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;twitter.svg&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;https://twitter.com/http_your_heart&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Mastodon&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;mastodon.svg&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;https://mastodon.art/@hyperlinkyourheart&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Instagram&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;instagram.svg&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;https://www.instagram.com/hyperlinkyourheart/&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;YouTube&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;youtube.svg&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;https://www.youtube.com/channel/UCc_O9Hp5UfQ-IHswi1H54Zg&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Twitch&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;twitch.svg&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;https://www.twitch.tv/hyperlinkyourheart&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Itch&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;itchio.svg&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;https://hyperlinkyourheart.itch.io/&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;GitHub&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;github.svg&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;https://github.com/khoulihan&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Atom Feed&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;rss.svg&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;/feeds/all.atom.xml&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;I like that I can just throw custom configuration into the config file and then make use of it in the templates. However, it probably makes the theme less generally&amp;nbsp;useful.&lt;/p&gt;
&lt;p&gt;As it stands currently, loading this post requires less than 30kb to be&amp;nbsp;transferred.&lt;/p&gt;
&lt;h3&gt;Plugins&lt;/h3&gt;
&lt;p&gt;Currently, the only plugin I&amp;#8217;m using is the &lt;a href="https://github.com/getpelican/pelican-plugins/tree/master/css-html-js-minify"&gt;css-html-js-minify&lt;/a&gt; plugin that is available in the pelican-plugins repository. I haven&amp;#8217;t found anything I need to write my own plugin to handle yet, but I&amp;#8217;m sure I will get to&amp;nbsp;it.&lt;/p&gt;
&lt;p&gt;One problem that needs solving is that the &lt;span class="caps"&gt;SVG&lt;/span&gt; icons are a big nuisance, because it doesn&amp;#8217;t seem to be possible to change their colour without using the &lt;span class="caps"&gt;CSS&lt;/span&gt; &lt;code&gt;filter&lt;/code&gt; property, which is not nearly as convenient as just setting the colour directly. In order to do that, using the &lt;code&gt;fill&lt;/code&gt; property, I would have to embed the SVGs, or reference them as symbols in a &lt;code&gt;&amp;lt;use&amp;gt;&lt;/code&gt; tag within an &lt;code&gt;&amp;lt;svg&amp;gt;&lt;/code&gt; tag. The individual icon files (from &lt;a href="https://fontawesome.com/"&gt;FontAwesome&lt;/a&gt;) aren&amp;#8217;t set up like that, and I didn&amp;#8217;t want to use their spritesheet because it is rather&amp;nbsp;large.&lt;/p&gt;
&lt;p&gt;What I might do in the future is write a plugin to compile the individual files into a single spritesheet of symbols, then find and replace any references to them with appropriate &lt;code&gt;&amp;lt;svg&amp;gt;&lt;/code&gt; tags. Essentially this will be doing the job that the FontAwesome toolkit usually does in the&amp;nbsp;browser.&lt;/p&gt;
&lt;h2&gt;The&amp;nbsp;Content&lt;/h2&gt;
&lt;p&gt;Uuuh&amp;#8230; I&amp;#8217;ll get back to you on that. Things I like, things I do, that sort of&amp;nbsp;thing.&lt;/p&gt;
&lt;h2&gt;Feedback&lt;/h2&gt;
&lt;p&gt;There&amp;#8217;s a couple of different strategies for allowing comments on a static site - I&amp;#8217;m not going to attempt them for now, and perhaps never will! If you have any feedback or thoughts there are many ways to reach me, such as &lt;a href="https://mastodon.art/@hyperlinkyourheart"&gt;Mastodon&lt;/a&gt; or &lt;a href="https://twitter.com/http_your_heart"&gt;Twitter&lt;/a&gt;, and I think that&amp;#8217;s just&amp;nbsp;fine.&lt;/p&gt;</content><category term="Meta"></category><category term="indieweb"></category><category term="blogging"></category><category term="pelican"></category></entry></feed>