<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
    <title>Richard Goulter's Blog</title>
    <subtitle><![CDATA[RSS feed for Richard Goulter's blog]]></subtitle>
    <link href="http://www.rgoulter.com/blog//atom.xml" rel="self" />
    <link href="http://www.rgoulter.com/blog/" />
    <id>http://www.rgoulter.com/blog//atom.xml</id>
    <author>
        <name>Richard Goulter</name>
        
        <email>richard.goulter+blog@gmail.com</email>
        
    </author>
    <updated>2026-06-09T00:00:00Z</updated>
    <entry>
    <title>Review of Elizabeth Boyle's "Tempted by the Night"</title>
    <link href="http://www.rgoulter.com/blog//posts/romance/2026-06-09-review-of-elizabeth-boyles-tempted-by-the-night.html" />
    <id>http://www.rgoulter.com/blog//posts/romance/2026-06-09-review-of-elizabeth-boyles-tempted-by-the-night.html</id>
    <published>2026-06-09T00:00:00Z</published>
    <updated>2026-06-09T00:00:00Z</updated>
    <summary type="html"><![CDATA[<p>I <em>have to</em> write a review of Elizabeth Boyle’s “Tempted by the Night”.</p>
<p>I loved reading this book, and I searched reviews online, and saw the other reviews didn’t do a good job of it.<br />
I mean, the other reviews just said “it’s a historical romance book with paranormal elements, and it’s okay I suppose”. Pfft.<br />
I’m fine with people not liking this book. Tastes vary. But I think it’d be a shame to overlook what this book was trying to do.</p>
<p>Boyle’s books are page-turners, certainly.<br />
She writes smart, witty dialogue. Her stories are very fun, and surprising. Boyle’s stories are filled with hidden identities and plots/dialogue which play with that.<br />
– I mean, saying it’s just a fun book with a silly story misses out on some of the smart storytelling.</p>
<p>I thoroughly enjoyed reading this book.</p>
<p>It’s very good.</p>
<p>But I gotta admit.<br />
“Tempted by the Night” is a difficult book to recommend.<br />
In a sense, I’d call it “unrecommendable”.<br />
As mentioned, it’s a historical romance with paranormal elements..</p>
<p>The first book in its series, at least, had a premise that sounds fun: the heroine makes a magic wish to “be the woman her crush loves”, and she wakes up as his mistress.<br />
Whereas with “Tempted by the Night”? Still set in regency England, but the heroine makes a magic wish, and becomes invisible. And the hero fights evil monsters.</p>
<p>I might try and put it a different way instead:<br />
The heroine has a crush on the hero, she’s certain that he’s secretly a good man even if everyone else thinks he’s a menace. She’s constantly anxious that her crush never sees her, though.<br />
Her magic wish to know him gives her invisibility, which gives her a disguise (as such) which lets her flirt with the hero, without her having to expose who she is.<br />
The hero? Indeed he does have a secret: He fights off evil monsters which come through portals. He has wicked, dangerous superhuman abilities that allow him to do this. He’s not sure he can find a wife who will understand.<br />
Plot-wise? The hero tries to uncover who this mysterious woman who’s got his heart is. And there’s a big evil monster with plans to invade the realm who he has to stop.</p>
<p>I think that’s a really cute use of the paranormal aspects to enhance the emotional tension.<br />
e.g. “Everything Everywhere All at Once” used fantastical elements to heighten a story of a woman with a strained relationship with her husband and her daughter. The film uses the surface-level narration with multi-verse tropes, as the story involves a mother trying to connect with her angsty daughter who’s struggling to find meaning.<br />
Here? It’s a romance between a shy wallflower character and a bad boy. The paranormal elements complement the emotional storytelling.</p>
<p>Spoiler warning:<br />
I don’t think the above would have spoiled my experience reading the story.<br />
I’d like to ramble or elaborate on some of these points below.<br />
If you’ve read that much and aren’t convinced it could be worth reading? You probably won’t have your enjoyment ruined if I discuss some more details about the story.</p>
<h2 id="rule-of-fun">Rule of Fun</h2>
<p>A well known rule in fiction is the “rule of cool”.</p>
<p>The “rule of cool” explains away all sorts of objections like “that’s dumb” or “that makes no sense”.</p>
<p>I reckon here, respect needs to be given to the “rule of fun”.</p>
<p>That is to say.. you can have a lot of fun if you allow the book to have fun. Even if some things about the story are dumb.</p>
<h2 id="my-favourite-moment-in-the-book">My Favourite Moment in the Book</h2>
<p>Throughout the plot, the hero is trying to discover the heroine’s identity.</p>
<p>She doesn’t have control over her invisibility; she’s invisible from sunset to sunrise. – So, during the day, the historical romance parts of the story get emphasised, and in the evening, the magical and intimate parts.</p>
<p>My favourite moment in the book occurs at a party where both the hero and heroine are in attendance:<br />
The gathering hold an archery competition among the women. The hero’s pretty hopeful to discover who his lover is, since she had bragged to him about being the best archer, even beating the popular mean girl.<br />
When it came time to shoot her shot, the heroine chickened out, not wanting to risk the hero discovering who she is and rejecting her.<br />
But as the sun sets, and no one is looking, she takes another shot and performs masterfully.<br />
– It’s such a great moment; the hero sees the archer and <em>knows</em> it’s his heroine because he knows her capability.</p>
<h2 id="thats-not-all">That’s Not All</h2>
<p>I have more thoughts on what I love about this book, Elizabeth Boyle’s books more broadly. – They’re not as widely read as they deserve to be. I’d like to write more on that later.</p>
<p>But I wanted to write this out first.</p>]]></summary>
</entry>
<entry>
    <title>Bridgerton S4 is Really Good</title>
    <link href="http://www.rgoulter.com/blog//posts/romance/2026-03-15-bridgerton-s4-is-really-good.html" />
    <id>http://www.rgoulter.com/blog//posts/romance/2026-03-15-bridgerton-s4-is-really-good.html</id>
    <published>2026-03-15T00:00:00Z</published>
    <updated>2026-03-15T00:00:00Z</updated>
    <summary type="html"><![CDATA[<p>Bridgerton Season 4 is really good!</p>
<p>Season 4 is primarily Benedict Bridgerton’s story.<br />
He meets a mystery lady at a masquerade ball, falls for her;
but it turns out she’s not suitable to marry…
Lots of flirting and angst ensues before the happily-ever-after.</p>
<p>It’s fun!</p>
<p>After watching Season 4,
I was so enthralled that I decided to re-read book 5
and was able to binge through Quinn’s cozy writing,
finishing the book in about a day.<br />
In contrast, Seasons 1-3 were awful enough
that they weren’t able to rekindle my love of reading.</p>
<p>Compared to Seasons 1-3,
Season 4 puts much more emphasis on what the show does well,
and dials down the stuff which brought down the previous seasons.</p>
<h2 id="season-4-is-more-fun-sweeter">Season 4 is More Fun, Sweeter</h2>
<p>I’ve always enjoyed the sweet parts of Netflix’s Bridgerton series.<br />
The sweet parts are easy to like, and the show never screwed the sweet parts up.</p>
<p>The parts of Season 4 I enjoyed the most were really able to bring the sweet
romance from the books to life,
but also making good use of the medium.<br />
For example, the opening scene of S4E2 re-plays the dance between Benedict and
Sophie in the garden, and this is re-played with the video effects of
fast-forwarding and re-winding as you’d do with a video.</p>
<p>But I guess my favourite moments from the season involved the ‘classical’
arrangements covering pop music; particularly pop music which is quite sweet.<br />
S4 had several of these moments!</p>
<p>A cover of Taylor Swift’s “Enchanted” is used to great effect:<br />
Following Sophie’s escape from the ball, Benedict is enthralled and on a mission
to find her.
When he finally does visit the house where she’s working as a maid,
she admires him through the gap in the door.<br />
The music plays as she’s excited to see him again but anxious about not being
able to meet him, and uncertain of whether he’d even recognise her.<br />
– So,
the music works really well as a reflection of the intense emotions going on.<br />
(And, indeed, the ass doesn’t recognise her when he later encounters her again).</p>
<p>Perhaps the cutest scene of the show has a cover of Paramore’s “All I Wanted”.<br />
I wasn’t familiar with this song. It’s also such a <em>good</em> song!<br />
But, in the show, the song still plays out over some intense emotions:<br />
Benedict has re-encountered Sophie. And though he didn’t recognise her,
circumstances bring them together:
he seeks shelter at his large country house,
but falls ill &amp; she helps him.
She is treated as a guest, and he spends the time flirting with her.
The two are each chastised about how inappropriate this all is:
he’s got power over her (she’s a maid), and maids who flirt are unemployable.<br />
At the same time, a secondary romance is playing out between Violet Bridgerton
and the charming Anderson.<br />
The song plays just as Benedict and Sophie are leaving this country estate
(knowing they can’t continue flirting),
and after Violet has confessed her anxieties
about a relationship with Anderson to him.</p>
<p>And the last one I really liked was
the cover of Olivia Rodrigo’s “Bad Idea Right” (which is a fun song).<br />
It plays out as Benedict and Sophie
(who are already trying hard to avoid each other)
encounter each other alone in the stairway.
They passionately make out with each other.</p>
<h2 id="season-4-does-a-better-job-with-secondary-plotlines">Season 4 Does a Better Job With Secondary Plotlines</h2>
<p>What the Seasons 1-3 did screw up was the content
that filled up the rest of the runtime
when the main romance wasn’t playing out.<br />
(In particular, S1 filled the rest with awfully mean and bitter drama,
and S3 filled it with dumb nonsense).</p>
<p>With S4, finally the show complements the main romance with
stories which are fun and cozy and emotional and about relationships.</p>
<p>That is.. I think Netflix’s Bridgerton is at its best when it sticks close
to the source material that makes it popular. The show is at its worst when the
show creators want to give attention to their original ideas.</p>]]></summary>
</entry>
<entry>
    <title>Org Babel is an Underappreciated Tool</title>
    <link href="http://www.rgoulter.com/blog//posts/programming/2025-10-26-org-babel-is-an-underappreciated-tool.html" />
    <id>http://www.rgoulter.com/blog//posts/programming/2025-10-26-org-babel-is-an-underappreciated-tool.html</id>
    <published>2025-10-26T00:00:00Z</published>
    <updated>2025-10-26T00:00:00Z</updated>
    <summary type="html"><![CDATA[<p>Emacs Org mode’s <a href="https://orgmode.org/manual/Working-with-Source-Code.html">source code blocks</a>
and the complementary <a href="https://orgmode.org/worg/org-contrib/babel/intro.html">org babel</a>
are underappreciated tools.</p>
<p><a href="https://www.gnu.org/software/emacs/">Emacs</a> is a <em>highly</em> customisable text editor.</p>
<p><a href="https://orgmode.org/manual/index.html">Org Mode</a> is a broad set of functionality for org markup files.<br />
Functionality ranges from todo list management &amp; note taking, agenda management,
to spreadsheets and document exporting/publishing.</p>
<p>Org mode’s support for source code allows blocks of source code to be:</p>
<ul>
<li>evaluated (with the results embedded in the document; i.e. “plaintext Jupyter Notebooks”),</li>
<li>exported (so the code can be presented in e.g. PDF documents),</li>
<li>tangled (rearranging snippets into source code into files for the compiler).</li>
</ul>
<p>Org Babel is the meta-programming functionality integrated with that,
which allows source code blocks of different languages to interact.</p>
<p>It’s an underappreciated tool. For how useful the functionality is,
it’s (at best) limited to Emacs users.</p>
<h2 id="what-ive-found-it-useful-for">What I’ve Found it Useful For</h2>
<p>“Plaintext Jupyter Notebooks” gets 95% of the way to describing org’s source code block evaluation.<br />
– “Plaintext”, on the one hand, means it lacks some user-friendliness that Jupyter Lab’s rich web interface provides.
On the other hand, plaintext has its own advantages.</p>
<p>Anyway. I’ve found the that “run the source code blocks, include the results in the document”
(also known as a <a href="https://en.wikipedia.org/wiki/Notebook_interface">“notebook”</a>) to be useful.</p>
<p>I’ve used it for:</p>
<ul>
<li><p>Playing around with / learning programming language subtleties.</p>
<ul>
<li>This is similar to how you’d use “scratch playgrounds” like https://play.rust-lang.org/:
being able to run a snippet of code and see the result, without having to fuss with
editing a file in some fresh location.</li>
</ul></li>
<li><p>Exploring the behaviour of under-documented APIs.</p>
<ul>
<li>Similar to how notebooks are useful for writing tutorials or documentation,
when I’ve been curious about how an under-documented API works,
I’ve found the notebook interface to be useful.</li>
</ul></li>
<li><p>DevOps work: spelunking around cloud environments and resources.</p>
<ul>
<li><a href="https://howardism.org/Technical/Emacs/literate-devops.html">howardism.org’s “Literate DevOps”</a>
is where I first came across the idea. – As well as the output of commands, a notebook
supports idiomatically embedding links to reference material, or explanation for why
commands were run. These are useful things for DevOps-related tasks.</li>
</ul></li>
</ul>
<h3 id="nice-to-have-properties-for-notebooks">Nice-to-Have Properties for Notebooks</h3>
<p>I suspect this takes a bit of practice to get right.</p>
<ol type="1">
<li><p>Easy to execute: notebooks are easy to execute for the notebook author.</p></li>
<li><p>Easy to export to a human-readable format: e.g. I want to be able to export the report to
markdown, and have the code in <em>that</em> be easy to read/understand, and easy to execute
(or at least easy to copy-and-paste).</p></li>
<li><p>Maintainable “As-Code”: The code snippets notebook should be maintainable (easy to read).
Ideally, the snippets have as little boilerplate as they need, and no less;
as much abstraction as helps make the code understandable, and no more.</p></li>
</ol>
<p>Sometimes I care about the results as a snapshot in time.
(e.g. With the output of commands when querying a system that’s in a malformed state..
it’d be useful to have a notebook which <em>reported</em> that bad state,
without overwriting those results with the latest (presumably correct) state).</p>
<h2 id="dynamics-executable-document-living-document-literate-programming">Dynamics: Executable Document, Living Document, Literate Programming</h2>
<p>Emacs org mode’s code block support enables some dynamics:</p>
<h4 id="executable-document">Executable Document</h4>
<p>Because the org mode’s source code blocks can be executed, with the results
embedded in the document, the resulting document can be described as an
“executable document”: it’s a document where you can execute the (embedded) code
snippets.</p>
<p>If the document’s emphasis is on the results (e.g. statistics/graphs), then this
can be described as a “reproducible research” tool. (To reproduce the results,
you’d just re-execute the document).</p>
<p>If the document emphasises documentation/specification, you can call it “executable documentation” or “executable specification”.<br />
i.e. the benefits of a specification (communicating what something is) with the
benefits of an automated check (confidence in the system’s correctness).</p>
<h4 id="living-document">Living Document</h4>
<p>A “living” document is a document which is fresh and maintained. (As opposed to
outdated).</p>
<p>In programming, ‘living documents’ often are associated with some kind of
automated execution or generation.</p>
<p>Executable documents can be maintained as living documents: you can be confident
the document is ‘up to date’ by re-executing it.</p>
<h4 id="literate-programming">Literate Programming</h4>
<p>I think ‘literate programming’ is broadly (mis)understood as programming where
the code and prose are freely mixed together.</p>
<p>I think a more accurate and precise definition is: literate programming
emphasises constructing an explanation of some module; where the literate
program can be ‘weaved’ to construct a beautifully typeset document; or
‘tangled’ to produce source code files for the compiler. – The emphasis is on
presentation of the program for a clear explanation (without being constrained
by order that a compiler would require).</p>
<p>Org mode’s source code supports this.<br />
Though, I suspect good ‘literate programming’ is requires a good amount of
discipline and thought.</p>
<h2 id="org-babel-is-meta-programming">Org-Babel is Meta-Programming</h2>
<p>From <a href="https://orgmode.org/worg/org-contrib/babel/intro.html">the org mode community’s introduction to org babel</a>:</p>
<blockquote>
<p>Because the return value of a function written in one language can be passed
to a function written in another language, or to an Org table, which is itself
programmable, Babel can be used as a meta-functional programming language.
With Babel, functions from many languages can work together. You can mix and
match languages, using each language for the tasks to which it is best suited.</p>
<p>For example, let’s take some system diagnostics in the shell and graph them
with R.</p>
</blockquote>
<p>The code blocks can be named. The code blocks can take in <code>:var</code> inputs, and can reference the output values from other code blocks.</p>
<p>Code blocks can be re-called with different arguments.</p>
<p>That is: writing an org document with source code blocks is itself programming.
It’s up to the programmer to choose an appropriate way of arranging the code.</p>
<p>I have not made much use of the <em>polyglot</em> nature of org-babel. But, I think it’s interesting.<br />
It’s easy to imagine how having a notebook with a mix of =shell= (e.g. bash) cells, some =python= shells could be useful.</p>
<h2 id="notebooks-interface-partway-between-manual-and-automated">Notebooks Interface: Partway Between “Manual” and “Automated”</h2>
<p>I think the most common ways to interact with some program are either entirely manual, or entirely automated.</p>
<p>e.g. if you’ve got some Python code, you’ll either run the code to observe its output;
or perhaps you’ll write some automated check to have confidence that the code works.</p>
<p>e.g. if you want to launch some VM, you’ll either launch it in a web console, or run a command line command;
or perhaps apply some Terraform code.</p>
<p>A notebook interface sits partway between these.</p>
<p>Unlike a script/automation: You can easily execute just the snippets you want, when you want. – If you’re interrupted
(e.g. some unexpected error occurs), you can either fix that externally, or embed instructions in the notebook to fix it.
– The instructions to fix it could even just be an explanation.<br />
This means a notebook does not need to be as robust as an automation/script.</p>
<p>Unlike manual commands: A notebook document can easily include commentary, and provides an easy way to read through
what was executed, and the output of what was executed.</p>
<p>I think those properties make notebooks a compelling tool for cases where you
don’t quite have a fully automated setup easily available.</p>]]></summary>
</entry>
<entry>
    <title>Rewriting smart-keymap's Key Storage Implementation</title>
    <link href="http://www.rgoulter.com/blog//posts/programming/2025-09-18-rewriting-smart-keymap-key-storage-implementation.html" />
    <id>http://www.rgoulter.com/blog//posts/programming/2025-09-18-rewriting-smart-keymap-key-storage-implementation.html</id>
    <published>2025-09-18T00:00:00Z</published>
    <updated>2025-09-18T00:00:00Z</updated>
    <summary type="html"><![CDATA[<p>I recently finished a <a href="https://github.com/rgoulter/smart-keymap/pull/381">big
rewrite</a> of how the keys
were stored in my <a href="https://github.com/rgoulter/smart-keymap">smart-keymap</a>
keyboard firmware.</p>
<h2 id="smart-keymap">Smart Keymap</h2>
<p>In case you’ve found this blogpost and aren’t familiar with <code>smart-keymap</code>:</p>
<p><code>smart-keymap</code> is a library for building keyboard firmware by declaring keymaps
in <a href="https://nickel-lang.org/">Nickel</a>, powered by
<a href="https://github.com/rgoulter/smart-keymap/blob/master/rust-lang.org">Rust</a>.</p>
<p>A ‘smart keyboard’ is a keyboard with additional bells and whistles, such as
alternate key functionality (layers, tap-hold keys, chords, etc.), or RGB
effects, etc. – e.g. <a href="https://docs.qmk.fm/">QMK</a>, <a href="https://zmk.dev/docs/">ZMK</a>
are popular smart keyboard firmware frameworks.</p>
<p>This project provides a library which handles the keymap behaviour part of this.
With <code>smart-keymap</code>, it’s easy to write keyboard firmware, in Rust <a href="https://github.com/rgoulter/smart-keymap/tree/master/stm32-embassy-smart-keyboard">using
embassy</a>
or
<a href="https://github.com/rgoulter/smart-keymap/tree/master/stm32f4-rtic-smart-keyboard">RTIC</a>,
or quickly provide powerful keymap functionality to <a href="https://github.com/rgoulter/smart-keymap/tree/master/firmware/ch32x035-usb-device-compositekm-c">EVT example code written in
C</a>.</p>
<h2 id="rewrite-motivation">Rewrite Motivation</h2>
<p>I was asked in a <a href="https://github.com/rgoulter/smart-keymap/issues/357">GitHub
issue</a> if was possible to
implement the sophisticated <a href="https://artsey.io">artseyio</a> in <code>smart-keymap</code>.</p>
<p>After brushing <code>smart-keymap</code>’s chording engine up a bit so that it supported
overlapping chords, I found that the artseyio keymap barely fit on the
low-budget CH32X035 MCU. (With a slightly older compiler version, I observed it
didn’t even fit).</p>
<p>I considered that the implementation (details discussed below) maybe wasn’t the
most efficient; I wanted the artseyio keymap firmware to fit on the CH32X035.</p>
<h2 id="the-numbers">The Numbers</h2>
<p>The rewrite can be found at pull request <a href="https://github.com/rgoulter/smart-keymap/pull/381">#381 of
smart-keymap</a>.</p>
<p>Overall, the summary says +6,783 −8,730.</p>
<p>The number added is smaller than the number deleted. I’m happy with that!</p>
<p>Though, this large number of lines changed does include changes to automatically
generated test snapshots.<br />
For an example of code that I touched, the git summary indicates
<code>ncl/keymap-codegen.ncl</code> had around 2000 lines of code changed.</p>
<p>As for the goal of reducing firmware size?<br />
For the example firmware built as a CI check, its size reduced from 78% of the
CH32X’s flash size, down to 54% of the firmware’s flash size.<br />
For the motivating artseyio keymap, its size reduced from 95% of the CH32X’s
flash size, down to 60%.</p>
<h2 id="rewrite-details-from-tree-based-key-storage-to-struct-of-arrays">Rewrite Details: from Tree-Based Key Storage, to Struct-of-Arrays</h2>
<p>The inspiration for writing <code>smart-keymap</code> was <a href="https://github.com/semickolon/kirei">semickolon’s
kirei</a>.
Kirei emphasised the somewhat unusual “keys as the main abstraction of keymap behaviour”.<br />
In the project’s discord channel, the author discussed all sorts of inspiring
ideas, like how a keymap engine is bears resemblance to a parser.. or how, if
you squint, various smart keyboard functionality (like tap hold, layers,
chords) can be generalised as varying the functionality depending on recent key
presses to the keymap.<br />
(That’s why kirei didn’t directly implement layering).</p>
<p>So.<br />
<code>smart-keymap</code> takes this idea of “the key as the main abstraction of keymap behaviour”,
and implements functionality like “tap hold keys” or “layered keys”.<br />
It was really natural to describe these as tree structures: a tap-hold key is a
key which acts like one key when tapped, as another key when held. It’s obvious
to describe this with a struct like <code>{ tap, hold }</code>. Similarly, a layered key
is a key which behaves differently depending on which layers are active; so, <code>{  base, layered: [...] }</code> is an obvious struct.</p>
<p>In <code>smart-keymap</code>’s case, the keys themselves are conceptually something like:</p>
<pre><code>   Key = Chorded { chord: Key, passthrough: Key }
       | Layered { base: Key, layered: Key }
       | TapHold { tap: Key, hold: Key }
       | HidKeyCode
       | LayerModifier
       | ...</code></pre>
<h4 id="tree-like-structs-on-the-stack">Tree-like Structs on the Stack</h4>
<p>There were a couple of problems of directly implementing the structs like this,
particularly for embedded firmware:</p>
<p>Similar to how with a linked list <code>List&lt;x&gt; = Node&lt;x&gt; | Empty</code>, <code>enum&lt;T&gt; {  Node(T), Empty }</code> doesn’t work in Rust,
a naive attempt at writing out these Key structs isn’t going to work.<br />
Roughly, the code needs to know what size the values are in order to call the function
(because the values are on the stack, not on the heap). And so it’s a problem if
the structure doesn’t have a determined size.</p>
<p>We can adjust the structure of the tree so that key values can’t be
arbitrarily deep:</p>
<pre><code>   Key = ChordedKey;
   ChordedKey = Chorded { chord: LayeredKey, passthrough: LayeredKey } | LayeredKey;
   LayeredKey = Layered { base: TapHoldKey, layered: TapHoldKey } | TapHoldKey;
   TapHoldKey = TapHold { tap: BaseKey, hold: BaseKey } | BaseKey;
   BaseKey = HidKeyCode | LayerModifier | ...`</code></pre>
<p>In order to express <code>ChordedKey = Chorded { chord, passthrough } | Layered</code>
without using the heap in Rust, I used <a href="https://github.com/rgoulter/smart-keymap/tree/46511224e803b3600e66d901799a593cdb0fde46/src/key/composite">a lot of ‘wrapping’
code</a>
to coerce the various values to be the same type (&amp; also indicate to the
compiler that the struct has finite size).<br />
<code>Chorded&lt;K&gt; { chord: K, passthrough: K }</code> requires that <code>chord</code> and <code>passthrough</code> have the same type;
which is a bit annoying if <code>chord</code> is an <code>HidKeyCode</code>, and <code>passthrough</code> is a <code>TapHold&lt;HidKeyCode&gt;</code>.</p>
<p>The size of each <code>key</code> value grows exponentially large, for the depth of this hierarchy of keys.<br />
For the struct above: a TapHold key is going to be twice as large as a base key,
a LayeredKey might be NumLayers times that.<br />
A ChordedKey will be twice that. (And, if <code>smart-keymap</code> were to have another kind of smart key that nests ChordedKeys…).</p>
<h4 id="flattening-the-tree-using-struct-of-arrays">Flattening the Tree using Struct-of-Arrays</h4>
<p>The idea to reduce storage size: instead of <code>TapHold&lt;K&gt; { tap: K, hold: K }</code>, we
have <code>TapHold { tap: Ref, hold: Ref }</code>, and a struct like <code>Keys { keyboard:  [...], tap_hold: [...], layered: [...], ... }</code>.</p>
<p>That is, rather than nesting the key data inside each key, the key definitions
are stored in a struct of arrays, and each key definition indirectly refers to
other keys using a <code>Ref</code>.<br />
e.g. a <code>Chorded</code> key’s <code>chord</code> might be <code>tap_hold::Ref(3)</code>; and looking up
<code>keys.tap_hold[3]</code> then refers to some <code>TapHold { tap: Ref, hold: Ref }</code> value.</p>
<p>Since each key definition only nests ‘references’, the size of the key data does
not grow exponentially as a result of a deeper hierarchy of key implementations.<br />
This also avoids all of the complicated fuss that was required for the previous nested
tree structure implementation.</p>
<p>:o) This is a very straightforward to change to make to the code.</p>
<p>I think if you squint, this is comparable to going from an AST-based
interpreter, to compiling the AST to instructions.</p>
<h4 id="lesson-learned">Lesson Learned</h4>
<p>Things you might be able to take away from this?</p>
<ul>
<li><p>For emdedded firmware, even low cost MCUs like the CH32X have enough flash
space that you can run fairly sophisticated code without needing to fuss over
having a particularly efficient implementation.</p></li>
<li><p>If firmware size is an issue, having using tree-like structs on the stack is
something to avoid.</p></li>
</ul>
<h2 id="rewrite-approach-a-pragmatic-all-in-one-go">Rewrite Approach: A Pragmatic All-in-One-Go</h2>
<p>I was impatient to implement this change.</p>
<p>It probably would have been possible to add indirection so that the old code
still worked, add in the new code (&amp; make use of the common functionality),
then shift over to the new code.</p>
<p>Instead, I opted to just rewrite in place.</p>
<p>The upside is: I wasn’t constrained by the old implementation.</p>
<p>One downside to this: this meant most of the functionality wasn’t going to work
until the rewrite was finished.</p>
<p>Another downside was that, the rewrite can’t then be split up into discrete
commits and have a working project. Because traits core to the implementation
were written, the tests would fail (or not even compile) for practically
everything more granular than the full rewrite.</p>
<h2 id="positive-about-rust-frictionless-refactoring">Positive About Rust: Frictionless Refactoring</h2>
<p>The rewrite basically involved smooshing two different traits (<code>key::Key</code> and
<code>key::KeyState</code>) into one (<code>key::System</code>).</p>
<p>Because the associated types and methods were similar, (and because I’d reshaped
the code a bit with “use nested Refs, not nested key data” in mind), much of
the rewrite was fairly straightforward.</p>
<p>This meant I rarely to re-think the details. And having a comprehensive integration test suite helped provide
confidence that the code was working as it should; or catch times when <a href="https://github.com/rgoulter/smart-keymap/pull/381#issuecomment-3296490855">I’d trip over Rust subtleties</a>.</p>
<p>Rust’s enums make for a really clean way of expressing different Rust states
some value might have. This made the rewrite flow.</p>
<h4 id="typo-preventer">Typo-Preventer</h4>
<p>Using Rust’s enums and pattern matching helps prevent typos.</p>
<p>In the <code>smart-keymap</code> codebase, the various smart key implementations (keyboard,
tap-hold, layered, etc.) get aggregated by a <code>composite</code> key. This composite
type has a <code>Ref</code> type like <code>enum Ref{ Keyboard(keyboard::Ref), TapHold(tap_hold::Ref), .. }</code>.</p>
<p>This sum type make it easier to retain type safety across code which dispatches
method calls to the respective key system implementations, helping to catch
typos from copy pasting.</p>
<h4 id="rusts-enums-help-keep-code-small">Rust’s Enums Help Keep Code Small</h4>
<p>The meat of this rewrite, where replacing <code>key::Key</code> and <code>key::KeyState</code> with
<code>key::System</code> involved the most thought, was in the <code>keymap::Keymap</code>
implementation itself.</p>
<p>I feel that by using enums, this helped reduce the amount of code I had to keep in mind at once.</p>
<p>For example, the <code>new_pressed_key</code> method returns a <code>PressedKeyResult</code> enum
(indicating whether the key press resulted in a pending key state, a resolved
key state, or the press of another key).<br />
Because this method returns an enum value, as opposed to calling back to a
method on <code>Keymap</code> or some other technique, I believe it’s easier to reason
about assumptions about what goes <em>into</em> the enum value, and assumptions are
made <em>from</em> using the enum value. – It’s easier to use correctly, and more
difficult to use incorrectly than an arbitrary method call.</p>]]></summary>
</entry>
<entry>
    <title>Brothers in Arms</title>
    <link href="http://www.rgoulter.com/blog//posts/games/2025-09-02-brothers-in-arms.html" />
    <id>http://www.rgoulter.com/blog//posts/games/2025-09-02-brothers-in-arms.html</id>
    <published>2025-09-02T00:00:00Z</published>
    <updated>2025-09-02T00:00:00Z</updated>
    <summary type="html"><![CDATA[<p>I recently played through the “Brothers in Arms” videogame series.</p>
<p>I played through the first game, “Brothers in Arms: Road to Hill 30” (which I hadn’t played before),
and replayed through its two sequels,
“Brothers in Arms: Earned in Blood” and “Brothers in Arms: Hell’s Highway”.</p>
<p>If you’ve never come across these before, then the quickest way of describing them is they’re like if “Band of Brothers” was a videogame. – The game takes place during WW2, and follows a squad from the 101st airbourne division through the 1944 invasion of France.</p>
<p>Gameplay-wise, it’s ostensibly a first-person shooter, but the game places significant emphasis on squad-based tactics.</p>
<p>What sets the first two games of the “Brothers in Arms” series apart from other first person shooters is its grounded realism. – The YouTube channel <a href="https://www.youtube.com/watch?v=Pl_SP9WHOsg">Animarchy History points out</a>, the missions closely resemble the after action reports from the historical events.</p>
<p>I really enjoyed the first two games, and I think the third one is comparatively trash. Here’s some elaboration:</p>
<h1 id="brothers-in-arms-road-to-hill-30">Brothers in Arms: Road to Hill 30</h1>
<p>BiA released in 2005.</p>
<p>For a comparison of tone, Medal of Honor’s Allied Assault (and its expansion packs) released in 2002-2003; Pacific Assault in 2004.
Call of Duty released in 2003.<br />
Tone-wise, these other games are all <em>very</em> action based, where you’re <em>the</em> hero who saves the day: Allied Assault has you almost entirely operating solo against hundreds of enemies (and tanks); Pacific Assault has you, an infantry soldier, pilot a dive-bomber and destroy a carrier and its destroyer; Call of Duty’s climax has you in a car chase as stuka bombers come after you.<br />
– Point is, these other games heavily put emphasis on spectacle and how “cool” things would be.</p>
<p>BiA, in contrast, is heavily focused on squad-based tactics, with a sombre story.<br />
The climax of the story isn’t an action-packed dash escaping dozens of bombers, but instead your squad defending the titular Hill 30.</p>
<p>Two things stick out about BiA: its good story, and its distinct, generally good gameplay.</p>
<p>The story opens in medias res. – Your squad is in the middle of fending off Germans, when the radioman, Leggett, breaks, and stands up clear in the open yelling “You fucking want me? Come take me! TAKE ME!” (and he’s then promptly blasted to bits).</p>
<p>The story then goes back to the chaotic air drop on June 6th, and follows Sgt Baker through his tour of France.<br />
The story is quite ‘emo’. Baker is an emotional character; throughout the story, several of his men die. He’s affected by it.<br />
The game does a good job at showing off just how young these characters are. At the start of the game, they’re giddily talking about how they shot some Germans, and talking about who’s a better superhero (Batman or Superman). By the end, they’re focused on the task they have to do.<br />
When the cutscenes show deaths, the game doesn’t shy away from showing the gore of mutilated bodies.</p>
<p>And the gameplay: again, to put in contrast with Medal of Honor or Call of Duty: in those games, pretty much you’re the engine of progress. You’ve got a gun, there are enemies, you shoot the enemies before they can shoot you.</p>
<p>At a glance of BiA’s gameplay, it might appear the same: it’s a WW2 first person shooter.<br />
But, BiA puts emphasis on squad based tactics. The enemies will be behind cover, you order your squad behind cover. Shooting at the enemy suppresses them.<br />
The level design generally encourages this formula: you use your squad (from cover) to suppress the enemy squad, and then you move around to an enemy’s flank, where you can shoot the enemy from a position where they aren’t behind cover.</p>
<p>As an example of how BiA varies from the FPS genre: In a typical FPS game, when there’s a tank, you either have no control of it; or, you’re driving the tank directly. In BiA, when you get a tank, you order it about without directly controlling it. The player character is able to mount the tank and use the machine-gun mounted on its turret.</p>
<p>In terms of things that aren’t great about the game?<br />
Well. The game is quite jank.<br />
In order to emphasise the squad-based tactics, the gunplay is quite bad. As intended, this discourages playing BiA as you would a typical FPS game. But, it also frustrates the player carrying out the flanking maneouver.<br />
The AI behaviour is also … rough. The ultimate section of the game gives you two tanks as the squads you order about. The tanks’ path finding is quite terrible, which somewhat undermines an otherwise satisfying denouement.<br />
The game is ‘hard’; but, some of the difficulty comes from being unfair. On the last level in particular, often you’ll be behind cover and <em>still</em> get take damage by enemies. When you try the same checkpoint over and over again, taking the same actions often leads to different outcomes. (Sometimes you take no damage, sometimes you take lots of damage).</p>
<h1 id="brothers-in-arms-earned-in-blood">Brothers in Arms: Earned in Blood</h1>
<p>Brothers in Arms: Earned in Blood was the first of the series that I played.</p>
<p>EiB released just 7 months after BiA. It’s best thought of as an expansion pack.</p>
<p>It’s a better game than BiA: it’s less unfair, its levels are more challenging, and it’s got some quality of life improvements.</p>
<p>In terms of what’s improved about its gameplay:</p>
<ul>
<li><p>The game has a “tactical view” which shows a bird’s eye view of the battlefield, and where units are. This further encourages tactical play. BiA had this; EiB improves upon it by letting you look around more, as well as zoom in/out on the units.</p></li>
<li><p>The game allows for infinite ammo for your weapons, by asking your squad mates for ammo. Practically, this encourages you to keep your squad mates alive, encourages you to use your ammo to suppress the enemy (rather than just reserving ammo for flanking) and reduces the pressure on racing through the game. (BiA isn’t <em>terrible</em> without this; but, EiB gives you less ammo to start with than BiA does).</p></li>
<li><p>New enemy type: the enemy’s 88mm artillery installation provides a very challenging threat to face! The 88 artillery can wipe out your whole squad in one shot, and can severly damage allied tanks before the tank can even get close.</p></li>
<li><p>New weapons: the game does have scoped FG42 weapons near the end. (Weapons and gunplay aren’t <em>really</em> the focus of BiA, though).</p></li>
<li><p>The AI is improved; I didn’t notice any jank pathfinding issues.</p></li>
<li><p>Better difficulty, less unfair: the things that made BiA hard have been smoothed out. (It’s much rarer to take damage while in cover or while getting to cover, gameplay is more consistent). The level design, while still emphasising squad-based tactics, has more challenging puzzles.</p></li>
</ul>
<p>I liked the story more.<br />
The story follows Sgt. Hartsock, who we saw as a side character in the first game, as he provides after action reports to Brigadier General S.L.A. Marshall. The story overlaps with BiA; showing BiA’s climactic battle from another perspective, and some battles afterwards.<br />
The story is less emo (Hartsock is not as senior as Baker, and has a more pragmatic view of the death of soldiers, even though he is still affected by their deaths).</p>
<p>If you had to play only one game from this series, this would be it.</p>
<h1 id="brothers-in-arms-hells-highway">Brothers in Arms: Hell’s Highway</h1>
<p>Brothers in Arms: Hell’s Highway released in 2008.</p>
<p>The game is generally well liked.<br />
But, in my opinion, compared to EiB or BiA, it’s relatively awful.</p>
<p>Broadly, the HH takes what’s good and distinct about BiA &amp; EiB,
and replaces it with a more casual, generic, and terrible game.</p>
<p>BiA and EiB had grounded stories with simple storytelling.<br />
HH emphasises spectacle and cinematic shots, with a dramatic and busy story full of intrigue.</p>
<p>BiA and EiB had gameplay with emphasised squad based tactics.<br />
HH frequently has you going solo throughout the maps, and simplifies level design so as to not need the supression and flanking from the previous games.</p>
<p>I did like some things about the game:</p>
<p>One thing I recall from its story, from when I played it years ago, was a scene where your squad is clearing out a factory in an industrial part of the city. They enter an elevator, then say to each other something like “oh, shit; the Germans <em>know</em> we’ll be coming; we’ll be met with an ambush”. Then the doors open … and there’s not a welcome party. It’s a tense scene, realistic, grounded.</p>
<p>In terms of things I liked about its gameplay:</p>
<ul>
<li><p>You get to control more types of squads. In BiA and EiB, you had a squad with rifles, and a squad with submachine guns. HH adds a squad with a machine gun, and a squad with a bazooka. The squad with the bazooka can be used to clear out certain kinds of enemies. – To an extent (and with potential), this should allow for interesting tactical puzzles: having to get your MG team into a good spot and then having it suppress the battlefield, or getting a bazooka team to a good spot to clear out a hostile fortification.</p></li>
<li><p>The health system is very clearly gamey, and well communicated. In BiA, you’d get hit randomly. HH has a slightly weird system where, if you’re out of cover, you increasingly risk getting killed, whereas when you’re behind cover, you won’t get hurt. – It’s <em>slightly</em> jank in that it requires you to be in “in cover” mode (rather than just “behind cover”); but, I think this complements the tactical gameplay.</p></li>
</ul>
<p>I think I’m more disappointed by the story than by the gameplay.<br />
The gameplay is simpler and easier, but the game would still work if the story remained grounded.</p>
<p>HH’s story doesn’t work for a number of reasons.</p>
<p>Two give a couple of concrete, demonstrative parts of the story:</p>
<p>Throughout HH, Leggett’s character appears almost like a ghost haunting Baker.<br />
The story builds up intrigue and mystery around Leggett.<br />
The story then has a <em>big reveal</em> about what <em>actually</em> happened with Leggett, Allen and Garnett.<br />
– The thing is, in BiA, one of the most dramatic parts of the game is the end of a mission where Baker and his squad take a farm from one side, while Leggett, Allen and Garnett clear it from the other side. At the end of the mission (in BiA), we find Leggett distressed and the dead bodies of Allen and Garnett. “There were too many…”, Leggett says. (To be clear, it comes across as an inexperienced soldier leading when he wasn’t ready or suited to).<br />
– HH revises this, and shows us that rather than being ‘too many’, instead the three of them were getting in a dumb argument, and a couple of Germans came along; Leggett didn’t do any fighting, and just (without any distress) let his squad mates die.</p>
<p>Fucking. What.</p>
<p>The game pairs this weird dramatization with a whole subplot about Baker’s pistol.<br />
Rumour has it that it’s cursed, and that everyone who touches it dies.<br />
I understand superstition in the army is absolutely a real thing, even to this day. But, HH portrays this in a tone out of line with its the more grounded storytelling of BiA/EiB.</p>
<p>A second part of the story I’d point to as a comparison with BiA/EiB’s story:<br />
Near the end of one of the penultimate mission in HH, you defend some railway station from an onslaught of soldiers and enemy tanks.<br />
In BiA, in the final mission you get to ride on the tank and experience being the cavalry that saves the day.<br />
In HH, you’re “saved” (well, you were doing fine anyway) by some friendly tanks showing up, and the camera shows the main guns firing with very cinematic shots.<br />
– Where BiA, it felt relieving to come across these tanks, in HH it’s just a game cutscene. It feels like they wanted to make a Cool Action Game.</p>
<p>As for why the gameplay doesn’t work?</p>
<p>Again, pretty much everything that made BiA/EiB distinct and interesting games has been stripped away.<br />
It feels like they wanted to make the game more appealing for a mainstream audience, but failed to preserve an interesting game for those who appreciated EiB.</p>
<p>For example, BiA/EiB were distinct for discouraging “just hang back and shoot enemies when they pop up from cover”, the formula which describes other FPS games.<br />
In HH, this is now a feasible way to play the game.</p>
<p>In BiA/EiB, machine guns presented a difficult challenge: they’d kill you in one hit if they hit you, so you had to navigate around the machine gun and clear it from its flank.<br />
In HH, you can just use your rifle to hit the machine gunner.</p>
<p>HH also dramatically simplifies the squad based tactics: as soon as your squad gets close to the enemy (which HH’s level design encourages), your squad just throws grenades at the enemy. – The suppression/flanking mechanics that defined EiB’s excellent levels are not particularly emphasised.</p>
<p>Keeping your squad alive is also not important.<br />
In HH, your squad revive to full health each checkpoint.<br />
What’s even the point?<br />
With BiA and EiB, I frequently felt that checkpoints covered two or three sections; you’d have to struggle to figure out each section, so would be re-playing sections of a checkpoint till getting to the part you were stuck. In HH, checkpoints are generously given.</p>
<p>88mm artillery, which was a HUGE, ultimate threat in EiB, is a joke in HH.<br />
Overall, the game is much easier.<br />
I played through all three games on difficult. BiA’s difficult is <em>hard</em>, quite unfair. EiB’s difficult is <em>hard</em>, and can take some time to find the right way to solve some problem. – I saw someone say they reckoned HH’s hardest difficulty was easier than BiA’s easiest (from someone who wasn’t great at games); I believe that.</p>
<p>In BiA/EiB, a tank was a powerful support unit, but not invincible. You’d have to protect it, to ensure you didn’t leave it vulnerable to enemy units with panzerfausts, or enemy antitank guns or enemy tanks. This creates a dynamic where you used your tank to suppress the enemy while you cleared out antitank threats; or, you’d suppress the antitank units while your tank advanced. – Tactical gameplay.<br />
In HH, you get to drive the tank, and the levels play out like arcadey gameplay. You play as the tank going solo. Zero tactical elements, since you can just blast enemy anti tank units, and you outmatch all enemy tanks. – It’s very action based, and plays out as any if in any generic action game.</p>
<p>HH also frequently has you clear through buildings by yourself. Again, this is in the direction of an action-based, typical FPS game.</p>
<p>I’m not sure what I’d think of HH if I’d not played BiA nor EiB.<br />
I’d probably really appreciate its squad controls and the game’s emphasis on use of cover.<br />
– It disappoints but doesn’t surprise that HH was the last BiA game they’ve made. I see on Wikipedia that they were going to make another one, but it was so divorced from what BiA was that they changed it to a different game. Wikipedia says another game is supposedly in development. – I’m not optimistic. If it’s taken years, that means it’s expensive; if it’s expensive, it will need to have mass market appeal. This means it’s unlikely to be distinct or different from any other FPS game.</p>]]></summary>
</entry>
<entry>
    <title>Factorio Space Age</title>
    <link href="http://www.rgoulter.com/blog//posts/games/2025-07-21-factorio-space-age.html" />
    <id>http://www.rgoulter.com/blog//posts/games/2025-07-21-factorio-space-age.html</id>
    <published>2025-07-21T00:00:00Z</published>
    <updated>2025-07-21T00:00:00Z</updated>
    <summary type="html"><![CDATA[<p>I completed a playthrough of Factorio Space Age. Here are my thoughts on it:</p>
<p>Factorio Space Age is an expansion-pack for Factorio.</p>
<p>Factorio is a game which involves constructing automated systems which mine
resources, and move/assemble these resources until ultimately constructing a
rocket.</p>
<p>Factorio stimulates the same creativity that programming or other engineering involves.<br />
It’s an enthralling game to play.</p>
<p>The Space Age expansion provides more content for the base game. The game adds
different planets on which to build factories, as well as interplanetary travel.
– Rather than building a rocket to win the game, you build a rocket to ferry
you (or items) to a spaceship. You build up the systems on these spaceships, and
have these spaceships take you (or items) to other planets. You build up more
factories on these other planets. The goal of the game in Space Age is to have a
sufficiently powerful spaceship which reaches the Solar System Edge.</p>
<p>Each new environment (space, and each of the new planets) offers a twist to the Factorio formula.</p>
<p>“First time as a scientist, second time as an engineer, third time as a
technician.” – The experience with Space Age is like playing Factorio
for the first time, for another few times.</p>
<h2 id="the-factorio-formula-and-the-twists">The Factorio Formula, and the Twists</h2>
<p>The planet you start out on plays out the same as a typical Factorio playthrough
does. – Though, since launching rockets is a fundamental aspect of Space Age,
launching rockets takes significantly fewer resources than it did in vanilla
gameplay.<br />
i.e.: The formula remains the same: your factory gathers inputs from ore patches
(iron, copper, stone, coal, and later, uranium), as well as from fluids (water,
crude oil), and processes these into increasingly complex intermediate products
that are used to assemble items.</p>
<p>Space Age’s new environments twist this formula a bit:</p>
<p>In Space, you build up spaceships (called “space platforms”). The space
platforms are loosely similar to Factorio’s trains: trains serve as a logistics
method for transporting items between stations; the space platforms analogously
transport items on fixed routes between planets.<br />
When traveling through space, the space platforms will need to shoot down
asteroids in their path. (This is the primary obstacle to reaching the game’s
goal: at each stage, the asteroids in space become tougher, and practically
require having unlocked another tier of automated weapons).<br />
The space platforms are constructed through sending items up from the planet.
(And the platforms can send items down to a planet).<br />
The space platform’s systems require gathering resources by collecting chunks of
asteroids, and processing these into fuel for the spaceship’s thrusters (as
well as into ammunition, etc.).</p>
<p>That’s already “Factorio, but different”: each platform needs to have the
systems to sustain itself: to build up enough fuel to get places, to have enough
ammunition to protect itself from asteroids, to have enough power to run those
systems.</p>
<p>The new planets vary the formula further. Space Age has four new planets; these
can be grouped into the three new inner planets, and an ‘outer planet’.</p>
<p>The inner planets can each be bootstrapped just with the resources you start
with on that planet. – I initially thought a Landing Pad building was required
for the space platforms to be able to send items down to the planet. But, it’s
possible to just import the items you need from another planet first;
bootstrapping the factory on each planet from scratch is not necessary!</p>
<p>The new inner planets are: Vulcanus (the lava one), Gleba (the biological one), Fulgora (the lightning one).</p>
<p>I started with Vulcanus.<br />
Compared to the base game, the tech tree has been shuffled around a bit. Of
course, some new toys are locked behind the technologies researched with science
from each of these new planets. – But, Vulcanus has artillery and cliff
explosives. (It’s somewhat frustrating that these can’t be unlocked on the
starting planet!).</p>
<p>Vulcanus does not twist the Factorio gameplay too much compared to vanilla.<br />
With Vulcanus, rather than mining iron and copper ore, instead you forge iron
and copper from lava. Rather than belts of iron and copper ore, you make use of
molten iron and copper.<br />
The oil-based product chain is also varied. Rather than processing crude oil,
Vulcanus flips the production chain: you start with what’s typically and end
product: sulfuric acid. The production chain for other oil-based products has a
different start than on the starting planet.</p>
<p>Gleba, the biological one, I’ve seen more mixed opinions about.<br />
It’s my understanding that the planet’s difficulty has been softened since the
initial release.<br />
I played on a ‘railworld’ difficulty. (Enemies didn’t expand their bases after I
destroyed their bases). With that, and the difficulty nerfing, I never faced any
pushback from the Gleba enemies.<br />
What I <em>did</em> face was significant confusion trying to figure Gleba out!<br />
On Gleba, rather than mining from ore patches, all the resources come from
growing and processing fruit.<br />
Additionally, the assembly machines which process the fruit take in nutrients as
fuel (i.e. they’re not powered by electricity).<br />
What makes this more complicated is that each of these items (the fruit, the
processed fruit, the nutrients) each spoil after some time. e.g. if a nutrient
is not used within a couple of minutes, it becomes spoilage, which can’t be
used. – Because spoilage can’t be used, your systems have to be able to deal
with these spoilable inputs.<br />
Somewhat dastardly, the Gleba science pack requires an alien egg as an input; if
the alien egg ‘spoils’, it hatches into an enemy unit.<br />
The Gleba science pack itself also spoils(!!).</p>
<p>It took me a long time to figure Gleba out.<br />
Ultimately, having just enough of a setup to construct <em>just</em> the Gleba science
is itself quite straightforward.</p>
<p>Fulgora, the lightning planet, is probably the most interesting new planet.<br />
Rather than building up new items from mined ores, with Fulgora, you mine scrap
items, and recycle the scrap down to intermediate products.<br />
This results in a funny twist wherein the blue circuit board, which is typically
a late-game intermediate product that’s complex to assemble, becomes relatively
common on Fulgora; whereas the green circuit board, which is typically an early
game intermediate product, becomes relatively difficult/expensive to acquire on
Fulgora.<br />
– But also.. because you’ll end up with an excess of items you don’t use from
the recycled scrap, you’ll naturally end up having to dispose of these excess items
in order to avoid blocking the factory’s production. This emphasises the <em>flow</em> of
the factory, and emphasises <em>streams</em> of items.</p>
<p>The final planet, Aquilo, is cold.<br />
Unlike the other planets, Aquilo does not support bootstrapping a factory just
from resources available on the planet. It requires using the space platforms to
import items.<br />
Aquilo’s gimmick is that, in addition to power, each building needs to be
adjacent to a heated pipe. This puts <em>some</em> constraint on how the factory is
arranged.<br />
It also makes getting started on Aquilo really interesting: solar power is very
limited on Aquilo, and it requires power to get water, and other power sources
require water to provide power.</p>
<h2 id="constrained-land">Constrained Land</h2>
<p>A common denominator across these new enviroments is that the area you have to
work with is relatively constrained.</p>
<p>On the starting planet, cliffs restrict where you can build. (Cliffs can be
cleared using cliff explosives, which require Vulcanus science packs to unlock).<br />
Vulcanus is limited by its cliffs, and lava. (Lava can be paved over with a
late-game item).<br />
Gleba’s land is swampy, and so often requires landfill. On the other hand, the
lack of cliffs/oceans means no chokepoints to serve as a defense.<br />
Fulgora’s land is limited to islands. Each island needs to be self-sufficient in
terms of power.<br />
Aquilo’s land is extremely limited.</p>
<p>I wouldn’t overstate this point. It’s not <em>too</em> complicated.<br />
But.<br />
Common advice to new players is to not condense the factory too much. By leaving
plenty of space between buildings in your factory, you’ve got room to make
adjustments and refinements to the factory. Whereas, a dense, compact factory
risks being difficult to make changes to.</p>
<h2 id="combat">Combat</h2>
<p>Factorio is not a combat-oriented game.</p>
<p>In the base game, combat plays a role as you expand your factory.<br />
But by mid/late game, you’ve likely automated your factory’s defenses, turning
defense into a logistic problem. The artillery unlocked in late game practically
eliminates challenge from attacking (turning attack into a logistic problem).<br />
– That is, it’s not like combat in Age of Empires or whatever, where combat is
a strategic aspect of your macroeconomic considerations.</p>
<p>With the new planets:
- In space, your platforms <em>must</em> shoot down enemy asteroids.
- Gleba’s enemies most closely resemble the enemies on the starting planet.
They’ll attack your factory if you pollute too much, as such.
- Vulcanus’s enemies are big and powerful, like mini-bosses. On Vulcanus, these
mini-bosses each guard a region. Once killed, you’re able to build in that
territory.
- Fulgora doesn’t have any combat/enemies.
- Aquilo doesn’t have any either.</p>
<h2 id="new-toys">New Toys</h2>
<p>In the base game, you unlock more powerful buildings which help make the game
easier.<br />
You unlock higher tiers of belts or assembling machines, which require more
complex intermediate products to build, but provide more useful buildings.<br />
e.g. At the start, your only option for power is burning coal to power steam
engines. Later in the game, you unlock solar power or nuclear power.</p>
<p>The Space Age expansion similarly provides more powerful items which make the
game easier.</p>
<p>Each of the inner planets has its own unique buildings which work well with
other planets.<br />
e.g. Vulcanus unlocks a big mining drill (which is a more efficient machine
compared to the electric mining drill), and a foundry (which provides a powerful
way to deal with smelting).</p>
<p>Probably the coolest new piece of equipment is the mech armour.<br />
In the base game, you upgrade your armour to the point where you can equip your
armour with a power generator, shields, and roboports.<br />
Mech Armour takes this one step further, giving you a jetpack. The jetpack has
two big benefits: it lets you fly over all terrain (rather than being hindered
by cliffs, or lava, or buildings), and it automatically flies if a train were to
collide with you.<br />
In the base game, trains were pretty much the deadliest threat to the character
in the late game. So, having the armour as a way to avoid that is very cool.</p>
<p>I liked the new military items.<br />
You get to unlock a rocket turret, as well as a tesla turret, and a railgun
turret.<br />
For weapons the player gets to wield, there’s a tesla gun, and a rail gun.<br />
The rail gun is a very fun late-game unlock which is <em>very powerful</em> against
Vulcanus’ demolisher enemies.<br />
The tesla gun is decidedly ‘meh’. It might be very good with some more bonus
damage technologies researched.</p>
<p>Late game also offers a new power generation method: fusion power.<br />
Factorio’s nuclear power is already strong as-is.<br />
Fusion Power is an even better power method. Nuclear power requires water, which
is a limited resource in some Factorio settings. Fusion power’s only input
requirements are its fuel cells.</p>
<p>Late game (into post game) also allows for capturing the enemy nests &amp; using
these. But, this wasn’t a system I made use of in game.</p>
<h2 id="quality">Quality</h2>
<p>The Space Age expansion also includes a “quality” mechanic. (“uncommon”, “rare”,
“epic”, “legendary”).<br />
Items can have a higher quality, which makes the item better. e.g. “rare
ammunition” will do more damage than normal ammunition.</p>
<p>I’ve seen on social media, this is not as loved as other parts of Factorio.<br />
I’ve also seen people point out there are currently ways to cheese the quality
mechanic; getting higher quality items in ways that’s obviously way easier than
what’s intended.</p>
<p>In my playthrough, I made slight use of the quality mechanic.<br />
On Fulgora, land is somewhat limited, and so it’s useful to be able to have
higher-quality buildings within the same restricted amount of land.</p>
<p>Generally, the quality mechanic is not “over powered”. It’s not exciting.</p>
<p>To me the biggest problem is that dealing with the quality is a big increase in
complexity.</p>
<p>To construct higher quality items, either the item must be constructed with
higher quality inputs, and/or the assembly machine constructing it must be
equipped with quality-enhancing modules.<br />
Inputs to an assembly machine must all be of the same quality tier. You can make
a rare item with rare-quality inputs; but, you can’t make an item with some
normal inputs, and some rare inputs.<br />
Quality-enhancing modules add non-determinism to the assembly machine’s output:
there’s a chance that the item is made with higher quality.</p>
<p>This results in a significant increase in complexity in factory layout.<br />
Because qualities can’t be mixed in input, your factory needs to consider five
times as many kinds of resources.</p>
<p>Unlike other systems in factorio (the logistic network, circuit network, trains,
space), quality does not feel exciting to design a factory for.<br />
Unlike other new toys (rail gun, fusion power), quality doesn’t feel
overpowered. (Although, well, I didn’t build many high quality items).<br />
Unlike higher tier items in factorio, I don’t feel it’s worth going for quality
items.</p>
<p>Of course, I could be mistaken about the quality system.<br />
Having even a marginal increase in value for buildings in a constrained space
could be worth the increase in resources to build. (e.g. That’s the rationale
behind the faster belts).<br />
And, it does offer a nice post-game challenge for players who find that
appealing.<br />
But, in my playthrough, I wasn’t interested.</p>
<h2 id="space-age-has-a-more-interesting-systems-sandbox">Space Age Has a More Interesting Systems Sandbox</h2>
<p>As with the vanilla game, in Space Age, the challenges are intrinsic to the
gameplay.<br />
The game has a bunch of systems you use to build up a factory.</p>
<p>The goal is somewhat arbitrary.<br />
The goal in the vanilla game is “launch one rocket”. A common post-game measure
was to see how much science-per-minute a factory could produce.<br />
In the Space Age expansion: reach the Solar System Edge.</p>
<p>I reckon the new planets that the Space Age expansion results in a more
interesting post-game sandbox.</p>
<p>Since each of the planets plays out slightly differently, there are different
advantages and disadvantages to producing items on each of them. – Perhaps the
player can aim for efficient trading by having each planet produce and export
what that planet is most efficient at, and importing everything else.</p>
<p>Fusion power is powerful. In my playthrough, my factory wasn’t large enough to
require anything more than the simplest fusion reactor setup. In the post-game,
it’s probably worth making use of fusion power everywhere.</p>
<p>The quality system provides an added challenge, and a concrete goal to reach
towards. The player can aim to scale up their factory by upgrading the
buildings to higher tiers.</p>
<h2 id="highlight-rescuing-a-spaceship-from-a-failure-cascade">Highlight: Rescuing a Spaceship from a Failure Cascade</h2>
<p>One highlight of my playthrough was having to rescue a spaceship in Aquilo
orbit:</p>
<p>I had a space platform that lingered in Aquilo orbit.<br />
Aquilo is the planet beyond the “inner planets”; the ultimate planet before the
journey to solar system edge.</p>
<p>In order to get to Aquio from the inner planets, your ship has to be quite
capable. It practically has to be equipped with rocket turrets, in addition to
the minigun turrets.<br />
And, of course, these turrets need to be supplied with ammo.</p>
<p>In designing the ship, I went with a simple modular design:<br />
The outer shell of the spaceship was a belt with ammunition, as well as a belt
of collected asteroid chunks.<br />
Within this shell, was a simple factory: modular units with a common bus of
input resources.</p>
<p>The design worked well enough so long as it got a balanced variety of asteroid
chunks. (i.e. all of metal, ice, and carbon).</p>
<p>I didn’t realise that Aquilo’s asteroids are 80% ice.<br />
This resulted in a case my design hadn’t considered; it was eventually starved
of metal, and so was unable to craft bullets.</p>
<p>(I thought I’d set up the space ship so that if it ran low on ammo, it’d make a
journey back to the starting planet, where it’s safe even without ammo. Somehow,
I hadn’t).</p>
<p>I was in a situation where the ship was beginning to run out of ammo.<br />
But, it’s also in Aquilo orbit, where it’s not safe to be out of ammo (&amp; also
requires some hefty spaceships in order to get to Aquilo).</p>
<p>It’s <em>usually</em> possible to play Factorio very conservatively in order to avoid
unexpected problems.<br />
Most of the time, the pressure is “I want to alleviate bottlenecks”.</p>
<p>This challenge required me to consider resources at my disposal. In the end, I
mitigated this by:</p>
<ul>
<li><p>Shipping over some laser turrets to Aquilo (so that these could be delivered
to the platform). Laser turrets consume a significant amount of electricity
(&amp; are not very effective vs anything larger than tiny asteroids), but, I
figured “anything is better than nothing”.</p></li>
<li><p>Tried shipping up some more bullets; but, by the game’s design, this isn’t an
effective solution.</p></li>
<li><p>Reduced how many thrusters the spaceship used. Before, the spaceship had a
full breadth of thrusters &amp; could go quite fast. But, if it goes slower, then
(per the game’s design), it doesn’t need as high a rate of ammo.</p></li>
<li><p>Bodged/reworked the spaceship’s factory setup of asteroid reprocessing. I was
able to get it to a state where it was recovering and (slowly) building up its
ammo stockpile.</p></li>
</ul>
<p>In the end, I was able to rescue the ship and get it back to the safe, starting
planet.</p>
<p>It wouldn’t have been the end of the game to have lost the spaceship.</p>
<p>But, the challenge provoked some creative problem solving, and made me consider
things I otherwise wouldn’t have.</p>
<h2 id="what-i-liked">What I Liked</h2>
<p>Railgun goes <em>thwip-pew</em>.</p>
<p>I mean. Yeah. The overpowered mechanics are great to unlock.</p>
<p>It’s tense the first time you have a spaceship travel to a new destination. –
So, the game’s goal of “reach the Solar System Edge” really does make you feel
like you’re testing a scrappy prototype of a ship.</p>
<p>I really enjoyed how the Space Age expansion tinkers with the Factorio formula,
which puts emphasis on different aspects of factory designs, challenging your
mental models for how you’ve approached problems before.</p>
<p>Despite how long the game takes to play, it was constantly engaging.</p>
<h2 id="what-i-disliked">What I Disliked</h2>
<p>Uhhh.</p>
<p>I did watch Dosh’s video on Space Age before playing the game. In that, he said
that it’s a shame that the factory on each planet didn’t need to be all that
large in order to beat the game.<br />
Space Age takes a long time to play. – In this sense, each new planet feels
like a puzzle. You have to figure out <em>how</em> to set up the factory on each of
the new planets; but, you don’t <em>need</em> to then iterate on your factory design so
that it’s much better than your initial setup.<br />
– By the time I reached the Solar System Edge, I hadn’t scaled up my science
production beyond my initial setup. On most planets, I had only one
science-producing assembler.</p>
<p>On the other hand, I think the weapons generally feel under-powered without
research bonuses.<br />
In the base game, you have tiered levels of bullets. Red, yellow, and
uranium…. (each gated behind tech). This feels like a nice way to solve the
problem: if you want more powerful stuff, you need a better factory. Passive
bonuses gated behind research <em>would</em> motivate a larger factory; but, so does
“build higher tier items”.</p>
<p>And I still think combat on the planets feels backwards.<br />
The endgame planet has no combat; whereas the starting planet faces the most pressing combat.<br />
I think an ideal progression might be the other way: starting out with no
combat, and progressing to increasingly hostile environments.</p>
<p>And of my playthrough, I think I missed out on the True Gleba Experience by
going for railworld.<br />
Because I played with the Railworld settings, enemies didn’t expand.<br />
And so, enemies didn’t get close enough to my farms on Gleba in order to be
triggered to attack.</p>]]></summary>
</entry>
<entry>
    <title>Example of How Rust Can be Confusing, Iteration and Refs</title>
    <link href="http://www.rgoulter.com/blog//posts/programming/2025-05-19-example-rust-confusion-iterating-over-ref.html" />
    <id>http://www.rgoulter.com/blog//posts/programming/2025-05-19-example-rust-confusion-iterating-over-ref.html</id>
    <published>2025-05-19T00:00:00Z</published>
    <updated>2025-05-19T00:00:00Z</updated>
    <summary type="html"><![CDATA[<p>I recently tripped up over this when writing Rust:</p>
<p>Consider this example, which features a few different ways
of iterating over arrays/vectors in Rust:</p>
<div class="sourceCode" id="cb1"><pre class="sourceCode rust"><code class="sourceCode rust"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="kw">fn</span> main() <span class="op">{</span></span>
<span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a>    <span class="kw">let</span> a<span class="op">:</span> [<span class="dt">u8</span><span class="op">;</span> <span class="dv">3</span>] <span class="op">=</span> [<span class="dv">3</span><span class="op">,</span> <span class="dv">7</span><span class="op">,</span> <span class="dv">5</span>]<span class="op">;</span></span>
<span id="cb1-3"><a href="#cb1-3" aria-hidden="true" tabindex="-1"></a>    <span class="kw">let</span> v<span class="op">:</span> <span class="dt">Vec</span><span class="op">&lt;</span><span class="dt">u8</span><span class="op">&gt;</span> <span class="op">=</span> <span class="pp">vec!</span>[<span class="dv">3</span><span class="op">,</span> <span class="dv">7</span><span class="op">,</span> <span class="dv">5</span>]<span class="op">;</span></span>
<span id="cb1-4"><a href="#cb1-4" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-5"><a href="#cb1-5" aria-hidden="true" tabindex="-1"></a>    <span class="cf">for</span> <span class="op">&amp;</span>x <span class="kw">in</span> <span class="op">&amp;</span>a <span class="op">{</span></span>
<span id="cb1-6"><a href="#cb1-6" aria-hidden="true" tabindex="-1"></a>        <span class="pp">println!</span>(<span class="st">&quot;{}&quot;</span><span class="op">,</span> x)<span class="op">;</span></span>
<span id="cb1-7"><a href="#cb1-7" aria-hidden="true" tabindex="-1"></a>    <span class="op">}</span></span>
<span id="cb1-8"><a href="#cb1-8" aria-hidden="true" tabindex="-1"></a>    <span class="cf">for</span> <span class="op">&amp;</span>x <span class="kw">in</span> a<span class="op">.</span>iter() <span class="op">{</span></span>
<span id="cb1-9"><a href="#cb1-9" aria-hidden="true" tabindex="-1"></a>        <span class="pp">println!</span>(<span class="st">&quot;{}&quot;</span><span class="op">,</span> x)<span class="op">;</span></span>
<span id="cb1-10"><a href="#cb1-10" aria-hidden="true" tabindex="-1"></a>    <span class="op">}</span></span>
<span id="cb1-11"><a href="#cb1-11" aria-hidden="true" tabindex="-1"></a>    <span class="cf">for</span> x <span class="kw">in</span> a <span class="op">{</span></span>
<span id="cb1-12"><a href="#cb1-12" aria-hidden="true" tabindex="-1"></a>        <span class="pp">println!</span>(<span class="st">&quot;{}&quot;</span><span class="op">,</span> x)<span class="op">;</span></span>
<span id="cb1-13"><a href="#cb1-13" aria-hidden="true" tabindex="-1"></a>    <span class="op">}</span></span>
<span id="cb1-14"><a href="#cb1-14" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-15"><a href="#cb1-15" aria-hidden="true" tabindex="-1"></a>    <span class="cf">for</span> <span class="op">&amp;</span>x <span class="kw">in</span> <span class="op">&amp;</span>v <span class="op">{</span></span>
<span id="cb1-16"><a href="#cb1-16" aria-hidden="true" tabindex="-1"></a>        <span class="pp">println!</span>(<span class="st">&quot;{}&quot;</span><span class="op">,</span> x)<span class="op">;</span></span>
<span id="cb1-17"><a href="#cb1-17" aria-hidden="true" tabindex="-1"></a>    <span class="op">}</span></span>
<span id="cb1-18"><a href="#cb1-18" aria-hidden="true" tabindex="-1"></a>    <span class="cf">for</span> <span class="op">&amp;</span>x <span class="kw">in</span> v<span class="op">.</span>iter() <span class="op">{</span></span>
<span id="cb1-19"><a href="#cb1-19" aria-hidden="true" tabindex="-1"></a>        <span class="pp">println!</span>(<span class="st">&quot;{}&quot;</span><span class="op">,</span> x)<span class="op">;</span></span>
<span id="cb1-20"><a href="#cb1-20" aria-hidden="true" tabindex="-1"></a>    <span class="op">}</span></span>
<span id="cb1-21"><a href="#cb1-21" aria-hidden="true" tabindex="-1"></a>    <span class="cf">for</span> x <span class="kw">in</span> v <span class="op">{</span></span>
<span id="cb1-22"><a href="#cb1-22" aria-hidden="true" tabindex="-1"></a>        <span class="pp">println!</span>(<span class="st">&quot;{}&quot;</span><span class="op">,</span> x)<span class="op">;</span></span>
<span id="cb1-23"><a href="#cb1-23" aria-hidden="true" tabindex="-1"></a>    <span class="op">}</span></span>
<span id="cb1-24"><a href="#cb1-24" aria-hidden="true" tabindex="-1"></a><span class="op">}</span></span></code></pre></div>
<p>That code works.</p>
<p>With the array <code>a</code>, it iterates over <code>&amp;a</code>, <code>a.iter()</code>, and <code>a</code>,
as well as with the vector <code>v</code> over <code>&amp;v</code>, <code>v.iter()</code>, and <code>v</code>.</p>
<p>Amending the example a bit, the following does not compile:</p>
<div class="sourceCode" id="cb2"><pre class="sourceCode rust"><code class="sourceCode rust"><span id="cb2-1"><a href="#cb2-1" aria-hidden="true" tabindex="-1"></a><span class="co">// Note: does NOT compile</span></span>
<span id="cb2-2"><a href="#cb2-2" aria-hidden="true" tabindex="-1"></a><span class="kw">fn</span> main() <span class="op">{</span></span>
<span id="cb2-3"><a href="#cb2-3" aria-hidden="true" tabindex="-1"></a>    <span class="kw">let</span> a<span class="op">:</span> [<span class="dt">u8</span><span class="op">;</span> <span class="dv">3</span>] <span class="op">=</span> [<span class="dv">3</span><span class="op">,</span> <span class="dv">7</span><span class="op">,</span> <span class="dv">5</span>]<span class="op">;</span></span>
<span id="cb2-4"><a href="#cb2-4" aria-hidden="true" tabindex="-1"></a>    <span class="cf">for</span> <span class="op">&amp;</span>x <span class="kw">in</span> <span class="op">&amp;</span>a <span class="op">{</span></span>
<span id="cb2-5"><a href="#cb2-5" aria-hidden="true" tabindex="-1"></a>        <span class="pp">println!</span>(<span class="st">&quot;{}&quot;</span><span class="op">,</span> x)<span class="op">;</span></span>
<span id="cb2-6"><a href="#cb2-6" aria-hidden="true" tabindex="-1"></a>    <span class="op">}</span></span>
<span id="cb2-7"><a href="#cb2-7" aria-hidden="true" tabindex="-1"></a>    <span class="cf">for</span> <span class="op">&amp;</span>x <span class="kw">in</span> a<span class="op">.</span>iter() <span class="op">{</span></span>
<span id="cb2-8"><a href="#cb2-8" aria-hidden="true" tabindex="-1"></a>        <span class="pp">println!</span>(<span class="st">&quot;{}&quot;</span><span class="op">,</span> x)<span class="op">;</span></span>
<span id="cb2-9"><a href="#cb2-9" aria-hidden="true" tabindex="-1"></a>    <span class="op">}</span></span>
<span id="cb2-10"><a href="#cb2-10" aria-hidden="true" tabindex="-1"></a>    <span class="cf">for</span> x <span class="kw">in</span> a <span class="op">{</span></span>
<span id="cb2-11"><a href="#cb2-11" aria-hidden="true" tabindex="-1"></a>        <span class="pp">println!</span>(<span class="st">&quot;{}&quot;</span><span class="op">,</span> x)<span class="op">;</span></span>
<span id="cb2-12"><a href="#cb2-12" aria-hidden="true" tabindex="-1"></a>    <span class="op">}</span></span>
<span id="cb2-13"><a href="#cb2-13" aria-hidden="true" tabindex="-1"></a>    <span class="cf">for</span> x <span class="kw">in</span> a <span class="op">{</span> <span class="co">// added this `for`</span></span>
<span id="cb2-14"><a href="#cb2-14" aria-hidden="true" tabindex="-1"></a>        <span class="pp">println!</span>(<span class="st">&quot;{}&quot;</span><span class="op">,</span> x)<span class="op">;</span></span>
<span id="cb2-15"><a href="#cb2-15" aria-hidden="true" tabindex="-1"></a>    <span class="op">}</span></span>
<span id="cb2-16"><a href="#cb2-16" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb2-17"><a href="#cb2-17" aria-hidden="true" tabindex="-1"></a>    <span class="kw">let</span> v<span class="op">:</span> <span class="dt">Vec</span><span class="op">&lt;</span><span class="dt">u8</span><span class="op">&gt;</span> <span class="op">=</span> <span class="pp">vec!</span>[<span class="dv">3</span><span class="op">,</span> <span class="dv">7</span><span class="op">,</span> <span class="dv">5</span>]<span class="op">;</span></span>
<span id="cb2-18"><a href="#cb2-18" aria-hidden="true" tabindex="-1"></a>    <span class="cf">for</span> <span class="op">&amp;</span>x <span class="kw">in</span> <span class="op">&amp;</span>v <span class="op">{</span></span>
<span id="cb2-19"><a href="#cb2-19" aria-hidden="true" tabindex="-1"></a>        <span class="pp">println!</span>(<span class="st">&quot;{}&quot;</span><span class="op">,</span> x)<span class="op">;</span></span>
<span id="cb2-20"><a href="#cb2-20" aria-hidden="true" tabindex="-1"></a>    <span class="op">}</span></span>
<span id="cb2-21"><a href="#cb2-21" aria-hidden="true" tabindex="-1"></a>    <span class="cf">for</span> <span class="op">&amp;</span>x <span class="kw">in</span> <span class="op">&amp;</span>v<span class="op">.</span>iter() <span class="op">{</span> <span class="co">// changed to &amp;v.iter()</span></span>
<span id="cb2-22"><a href="#cb2-22" aria-hidden="true" tabindex="-1"></a>        <span class="pp">println!</span>(<span class="st">&quot;{}&quot;</span><span class="op">,</span> x)<span class="op">;</span></span>
<span id="cb2-23"><a href="#cb2-23" aria-hidden="true" tabindex="-1"></a>    <span class="op">}</span></span>
<span id="cb2-24"><a href="#cb2-24" aria-hidden="true" tabindex="-1"></a>    <span class="cf">for</span> x <span class="kw">in</span> v <span class="op">{</span></span>
<span id="cb2-25"><a href="#cb2-25" aria-hidden="true" tabindex="-1"></a>        <span class="pp">println!</span>(<span class="st">&quot;{}&quot;</span><span class="op">,</span> x)<span class="op">;</span></span>
<span id="cb2-26"><a href="#cb2-26" aria-hidden="true" tabindex="-1"></a>    <span class="op">}</span></span>
<span id="cb2-27"><a href="#cb2-27" aria-hidden="true" tabindex="-1"></a>    <span class="cf">for</span> x <span class="kw">in</span> v <span class="op">{</span> <span class="co">// added this `for`</span></span>
<span id="cb2-28"><a href="#cb2-28" aria-hidden="true" tabindex="-1"></a>        <span class="pp">println!</span>(<span class="st">&quot;{}&quot;</span><span class="op">,</span> x)<span class="op">;</span></span>
<span id="cb2-29"><a href="#cb2-29" aria-hidden="true" tabindex="-1"></a>    <span class="op">}</span></span>
<span id="cb2-30"><a href="#cb2-30" aria-hidden="true" tabindex="-1"></a><span class="op">}</span></span></code></pre></div>
<p>(<a href="https://play.rust-lang.org/?version=stable&amp;mode=debug&amp;edition=2024&amp;gist=de9ba10ffef8431cafce3750b0ddb276">Link to play.rust-lang.org</a>)</p>
<p>What doesn’t work:</p>
<ol type="1">
<li>The expression <code>&amp;v.iter()</code> is incorrect.</li>
</ol>
<ul>
<li>i.e. <code>v.iter()</code> is implicitly the same as <code>(&amp;v).iter()</code>,
whereas <code>&amp;v.iter()</code> is the same as <code>&amp;(v.iter())</code>.
<ul>
<li>I find it intuitive that <code>&amp;(v.iter())</code> cannot be an iterator,
since it’s an immutable reference.</li>
</ul></li>
</ul>
<ol start="2" type="1">
<li>The <code>for x in v { ... }</code> consumes the value of <code>v</code>;
so, <code>v</code> can’t be used after this.</li>
</ol>
<ul>
<li>Perhaps what’s a bit confusing is that the <code>for x in a { ... }</code> <em>can</em>
be written out twice. – But, here the code’s a bit sneaky:
<code>a</code> can be used in multiple <code>for x in a { ... }</code> loops because
<code>a</code> has type <code>[u8; 3]</code>, and the <code>u8</code> type implements <code>Copy</code>,
and so <code>a</code> is copied when writing out <code>for x in a { ... }</code> twice.</li>
</ul>
<p>Some notes for reference regarding the first point:</p>
<ul>
<li><p>The Rust docs page for Vec does have the <a href="https://doc.rust-lang.org/std/vec/struct.Vec.html#method.iter">iter method</a>.
It’s under “Methods from Deref&lt;Target = [T]&gt;”.
This is the same as <a href="https://doc.rust-lang.org/std/primitive.slice.html#method.iter">the slice primitive’s iter method</a>.</p></li>
<li><p>Rust’s <code>Vec</code> has all the methods that Rust’s <code>slice</code> has,
because <a href="https://doc.rust-lang.org/std/vec/struct.Vec.html#impl-Deref-for-Vec%3CT,+A%3E">Vec implements <code>Deref</code> for slice</a>,
and Rust has <a href="https://doc.rust-lang.org/std/ops/trait.Deref.html#deref-coercion">implicit deref coercion</a>.
That is, any Vec can be used as a slice.</p></li>
<li><p>The <code>iter</code> method that <code>Vec</code> uses has signature:</p>
<pre><code>pub fn iter(&amp;self) -&gt; Iter&lt;&#39;_, T&gt;</code></pre>
<ul>
<li>Rust automatically converts the <code>v</code> in <code>v.iter()</code> to <code>(&amp;v).iter()</code>.
This is called ‘auto-referencing’.
<ul>
<li>https://doc.rust-lang.org/book/ch05-03-method-syntax.html</li>
<li>https://doc.rust-lang.org/reference/expressions/method-call-expr.html#r-expr.method.candidate-receivers-refs</li>
</ul></li>
</ul></li>
</ul>
<p>There’s no <em>single</em> thing about the above which I’d consider too confusing.</p>
<p>The documentation/references above are all for things which pretty much “just work”.
And one mistaken assumption in the above code is about operator precedence.</p>
<p>I wanted to write about this because it’s a subtle case where incorrect expressions looked
very similar to correct expressions.</p>
<p>It’s an example of something that I found surprising when writing Rust, and it’s
not something that I’d have to think about if I were writing Python or
TypeScript. (Or, the other way: it’s not something I’d have to think about when
writing C).</p>]]></summary>
</entry>
<entry>
    <title>Announcing a new Smart Keymap Library</title>
    <link href="http://www.rgoulter.com/blog//posts/programming/2025-05-06-announcing-a-new-smart-keymap-library.html" />
    <id>http://www.rgoulter.com/blog//posts/programming/2025-05-06-announcing-a-new-smart-keymap-library.html</id>
    <published>2025-05-06T00:00:00Z</published>
    <updated>2025-05-06T00:00:00Z</updated>
    <summary type="html"><![CDATA[<p>For the last few months, I’ve been working on a new “smart keymap” library.</p>
<p>The code is published over at: <a href="https://github.com/rgoulter/smart-keymap" class="uri">https://github.com/rgoulter/smart-keymap</a>.</p>
<p>This post is to introduce this, go over what it is, and why I’ve found it
exciting to work on.</p>
<h1 id="smart-keymap">“Smart Keymap”</h1>
<p>By “smart keymap”, I mean the keymap behaviour for “smart keyboards”.</p>
<p>And by “smart keyboards”, I mean keyboards like those which run QMK or ZMK firmware.<br />
(I first saw the term used on the <a href="https://sites.google.com/alanreiser.com/handsdown">Hands Down
Layout</a> webpage).</p>
<p>Smart keyboards often provide functionality like “Layers” or “Tap-Hold” keys,
which allow the keys to have sophisticated behaviour.</p>
<p>For example, these features allow bringing the full functionality of the
keyboard to within easy reach of the hands resting on home row. (e.g. <a href="https://www.youtube.com/watch?v=bQUx2KtJ6TA">here’s a
video demonstrating how the miryoku layout does
this</a>).</p>
<h2 id="what-the-project-is">What the Project is</h2>
<p>The smart keymap library allows declaring a “smart keymap” which can be used as
part of a smart keyboard’s firmware.</p>
<p>The keymap is declared using <a href="https://nickel-lang.org/">the Nickel configuration
language</a>, a nifty “JSON + functions + types”
language.<br />
(If you’re familiar with the <a href="https://github.com/semickolon/fak">FAK keyboard
firmware</a>, you can understand the smart
keymap libary as like FAK, but targetting a broader set of MCUs than just the
CH55x).</p>
<h3 id="example-keymap">Example Keymap</h3>
<p>To illustrate, here’s an example keymap declared in Nickel for a 5x15
ortholinear keyboard:</p>
<pre class="nickel"><code>let K = import &quot;keys.ncl&quot; in

let DVORAK_  = 0 in
let GAMING_  = 1 in
let RAISE_   = 2 in
let LOWER_   = RAISE_ + 1 in
let ADJUST_  = RAISE_ + 2 in

{
  chords =
    let K = import &quot;keys.ncl&quot; in
    [
        { indices = [48, 49], key = K.LeftGUI &amp; K.PageUp, },
        { indices = [55, 56], key = K.LeftGUI &amp; K.PageDown, },
    ],

  config.tap_hold.interrupt_response = &quot;HoldOnKeyTap&quot;,

  custom_keys = fun K =&gt;
    let HoldLayerMod = fun layer_index =&gt; K.hold (K.layer_mod.hold layer_index) in
    {
      DVOR = K.layer_mod.set_default DVORAK_,
      GAME = K.layer_mod.set_default GAMING_,

      ENT_R = K.Return &amp; HoldLayerMod RAISE_,
      ESC_L = K.Escape &amp; HoldLayerMod LOWER_,
      ADJ   = K.layer_mod.hold ADJUST_,

      # Tap-Hold Home Row Modifiers
      A_A = K.A &amp; K.hold K.LeftAlt,
      G_O = K.O &amp; K.hold K.LeftGUI,
      C_E = K.E &amp; K.hold K.LeftCtrl,
      S_U = K.U &amp; K.hold K.LeftShift,

      S_H = K.H &amp; K.hold K.RightShift,
      C_T = K.T &amp; K.hold K.LeftCtrl,
      G_N = K.N &amp; K.hold K.RightGUI,
      A_S = K.S &amp; K.hold K.LeftAlt,

      # Sticky modifier keys
      SK_S = K.sticky K.LeftShift,
      SK_C = K.sticky K.LeftCtrl,
      SK_G = K.sticky K.LeftGUI,
      SK_A = K.sticky K.LeftAlt,
    },

  layers = [
    # Base: Dvorak
    m%&quot;
      `    1    2    3   4     5   XXXX XXXX XXXX 6    7     8    9    0    DEL
      TAB  &#39;    ,    .   P     Y   7    8    9    F    G     C    R    L    BSPC
      ESC  A_A  G_O  C_E S_U   I   4    5    6    D    S_H   C_T  G_N  A_S  /
      LSFT ;    Q    J   K     X   1    2    3    B    M     W    V    Z    RSFT
      LCTL LGUI LALT TAB ESC_L SPC SPC  0    BSPC BSPC ENT_R DEL  RALT RGUI RCTL
    &quot;%,
    # Base: Gaming
    m%&quot;
      `    1    2    3   4     5   XXXX XXXX XXXX 6    7     8    9    0    DEL
      TAB  Q    W    E   R     T   XXXX XXXX XXXX Y    U     I    O    P    BSPC
      ESC  A    S    D   F     G   XXXX XXXX XXXX H    J     K    L    ;    &#39;
      LSFT Z    X    C   V     B   XXXX XXXX XXXX N    M     ,    .    /    RSFT
      LCTL LGUI LALT TAB ESC_L SPC XXXX XXXX XXXX BSPC ENT_R DEL  RALT RGUI RCTL
    &quot;%,
    # Raise
    m%&quot;
      TTTT TTTT TTTT TTTT TTTT TTTT TTTT TTTT TTTT TTTT TTTT TTTT TTTT TTTT TTTT
      `    1    2    3    4    5    TTTT TTTT TTTT 6    7    8    9    0    \
      DEL  F1   F2   F3   F4   F5   TTTT TTTT TTTT F6   -    =    [    ]    /
      TTTT F7   F8   F9   F10  F11  TTTT TTTT TTTT F12  TTTT TTTT TTTT TTTT TTTT
      TTTT TTTT TTTT TTTT ADJ  TTTT TTTT TTTT TTTT TTTT TTTT TTTT TTTT TTTT TTTT
    &quot;%,
    # Lower
    m%&quot;
      TTTT TTTT TTTT TTTT TTTT TTTT TTTT TTTT TTTT TTTT TTTT TTTT TTTT TTTT TTTT
      ~    !    @    #    $    %    TTTT TTTT TTTT ^    &amp;    *    (    )    |
      INS  F1   F2   F3   F4   F5   TTTT TTTT TTTT F6   _    +    {    }    ?
      TTTT F7   F8   F9   F10  F11  TTTT TTTT TTTT F12  TTTT HOME PGDN PGUP END
      TTTT TTTT TTTT TTTT TTTT TTTT TTTT TTTT TTTT TTTT ADJ  LEFT DOWN UP   RGHT
    &quot;%,
    # Adjust
    m%&quot;
      DVOR GAME TTTT TTTT TTTT TTTT TTTT TTTT TTTT TTTT TTTT TTTT TTTT TTTT TTTT
      TTTT BOOT TTTT TTTT TTTT TTTT TTTT TTTT TTTT TTTT PSCR SCRL PAUS TTTT TTTT
      CAPS SK_A SK_G SK_C SK_S TTTT TTTT TTTT TTTT TTTT SK_S SK_C SK_G SK_A CWTG
      TTTT TTTT TTTT TTTT TTTT TTTT TTTT TTTT TTTT TTTT TTTT TTTT TTTT TTTT TTTT
      TTTT TTTT TTTT TTTT TTTT TTTT TTTT TTTT TTTT TTTT TTTT TTTT TTTT TTTT TTTT
    &quot;%,
  ],
}</code></pre>
<p>Using an expressive language like Nickel has some compelling advantages for
text-based or declarative keymap configuration, compared to what’s required
to write keymaps for other keyboard firmware.</p>
<p>e.g. QMK’s <a href="https://docs.qmk.fm/keymap">keymaps are written in C</a>, or a <code>keymap.json</code>
<a href="https://docs.qmk.fm/newbs_building_firmware_workflow#add-a-json-keymap">as downloaded from the online QMK
Configurator</a>.</p>
<p>e.g. <a href="https://zmk.dev/docs/config">ZMK uses kconfig and devicetree files for its
configuration</a>.</p>
<p>Using C to write keymaps allows for a breadth and depth of custom implementation
of behaviour.<br />
C is relatively simple. C &amp; CPP are somewhat limited and cumbersome when it comes
to making abstractions, such as re-using keymap functionality across keymaps.</p>
<p>Using a configuration language to define a keymap limits what the keymap can configure
to a smaller interface. But, Nickel’s highly expressive features allow it to construct
abstractions and reuse/customization more elegantly than CPP. – A Nickel keymap
configuration is going to be easier to maintain or re-use than code which makes heavy use
of CPP.</p>
<p>(Using Rust to write keymaps, as Keyberon does: it’s practically nicer to use
Rust than to make use of CPP; but, Rust’s strictness make it practically much more
difficult for Rust code to match how bespoke a keymap’s implementation can be.
Certainly in the case of QMK keymaps vs keyberon keymaps. – <a href="https://github.com/rgoulter/keyboard-labs/tree/master/firmware/keyberon/src/layouts">My own keymaps written in
Rust can be found here</a>).</p>
<h3 id="context-and-motivation-the-keymap-abstraction">Context and Motivation: The Keymap Abstraction</h3>
<p>From my experience <a href="https://github.com/rgoulter/keyboard-labs/tree/master/firmware/keyberon">writing firmware for different keyboards / MCUs with
keyberon</a>,
in order to best re-use code across those different firmware binaries, a fairly
natural pair of abstractions emerged: the first was abstracting over the logic
which produces input key press/release events from matrix scanning; the second,
abstracting over the mechanism which produces HID keyboard reports from those
key press/release events. – I’d describe the first as the “keyboard” or “keyboard
frontend”, the second as the “keymap” or “keyboard backend”.</p>
<p>Essentially, the smart keymap’s interface is along the lines of
<code>Keymap { handle_event(InputEvent); tick() -&gt; Output; }</code>.</p>
<p>Later, I made a prototype keyboard which used the CH58x MCU. The CH58x EVT
provides example firmware code, including an example HID keyboard, and a BLE
keyboard. – The keymap logic in these examples is essentially “in a loop, tap a
b c d …”. – That is, to make a practical keyboard firmware from the EVT
examples, you’d need to implement matrix scanning, and implement the keymap
behaviour. So, the same abstraction which was useful for code-reuse in writing keyberon
firmware would also be useful for writing keyboard firmware for the CH58x.</p>
<h3 id="keymap-not-keyboard">Keymap, not Keyboard</h3>
<p>The smart-keymap library’s scope is limited to “keymap functionality”, rather
than the broader scope of “keyboard functionality”.</p>
<p>This allows for the project to have a narrower focus on what it supports.</p>
<p>‘Keymap features’ are the behaviours from manipulating inputs; ‘keyboard features’
are related to physical parts of the keyboard.<br />
e.g. “tap hold key” functionality is definitely a keymap feature.<br />
Whereas e.g. “split keyboard” functionality is definitely a keyboard feature.
(The keymap behaviour would be the same whether the keyboard is split or not).<br />
For things like “change the RGB effect”, the keymap doesn’t need to know how the
RGB system is implemented. – All the keymap needs to provide is a way of invoking things
like “change the RGB effect”.</p>
<h3 id="library-not-framework">Library, not Framework</h3>
<p>Providing smart keymap functionality as a library also narrows the project’s
scope compared to writing an entire (keyboard) firmware framework.</p>
<p>Practically, I think it’s always going to be easier to bring up keyboard
firmware for some unsupported MCU by integrating a library into the an HID
keyboard example from an EVT, compared to the effort it’d take to adapt the EVT
into the platform-supporting code of a framework.</p>
<h1 id="why-this-has-been-exciting-to-write">Why This Has Been Exciting to Write</h1>
<h2 id="nickel-is-a-powerful-config-language">Nickel is a Powerful Config Language</h2>
<p>I first came across <a href="https://nickel-lang.org/">Nickel</a> in relation to
the <a href="https://nixos.org/">Nix package manager</a>. – Nix is pretty neat;
so Nix-related things are likely to be neat.</p>
<p>Nickel, at a glance, is “JSON + functions (+ types)”.</p>
<p>The smart-keymap library obviously draws a lot of inspiration from
<a href="https://github.com/semickolon/fak">semickolon’s fak</a> and
<a href="https://github.com/semickolon/kirei">kirei</a> projects; both of which are keyboard
firmware which use Nickel for configuring the keymap.</p>
<p>My initial experience working with fak can be found in my
<a href="https://github.com/rgoulter/fak-config">fak-config</a>.</p>
<p>After having written much more Nickel code for smart-keymap,
a couple of things which stand out are its contracts,
and its modular configuration.</p>
<h3 id="nickel-contracts">Nickel Contracts</h3>
<p><a href="https://nickel-lang.org/user-manual/correctness/">The Nickel manual discusses how the language helps write ‘correct’
configurations</a>.</p>
<p>It took me some time to build an intuition for use of contracts.</p>
<p>Roughly, in Nickel, ‘types’ are only ever the concrete shape of the values.
e.g. Number, String, Array of type, Record of type. Types are checked
statically. Nickel doesn’t have user-defined types / aliases.</p>
<p>Whereas, contracts are a more expressive mechanism for describing more abstract
aspects of &amp; assertions about a value. Contracts are evaluated at runtime.
Contracts can be user defined; and can even be constructed from predicate or
validator functions on values.</p>
<h3 id="nickel-modular-configuration">Nickel Modular Configuration</h3>
<p><a href="https://nickel-lang.org/user-manual/modular-configurations/">The Nickel manual discusses how its record merging enables modular configuration</a>.</p>
<p>In the Nix ecosystem, several tools make use of modules. e.g. NixOS
configuration is done with modules, <a href="https://devenv.sh/">DevEnv</a> configuration
modules, <a href="https://flake.parts/">flake-parts</a> provides an interface for writing
flakes as modules.</p>
<p>Modules provide a way of writing code as composable elements.</p>
<p>Nickel’s records are evaluated lazily, &amp; can be combined, which allows Nickel
records to be used as modules.</p>
<p>In the smart-keymap codebase, the <a href="https://github.com/rgoulter/smart-keymap/tree/master/firmware/ch32x035-usb-device-compositekm-c/ncl/codegen">codegen for the CH32X
firmware</a>
is written in a modular style.<br />
e.g. <code>keyboard_split.ncl</code> is a module which describes what its inputs are, what
contracts it expects those inputs to satisfy, and its outputs.</p>
<p>How might that apply to keymaps?<br />
For example: a keymap might be defined using colemak by default, <code>{ alphas | default = colemak_dh, .. }</code>.
Nickel syntax would then allow re-using this keymap and changing <code>alphas</code> with <code>other_keymap &amp; { alphas = qwerty }</code>.</p>
<h2 id="inspirations-kirei-and-key-as-the-primary-keymap-abstraction">Inspirations: Kirei, and “Key as the primary keymap abstraction”</h2>
<p>Aside from using Nickel as the configuration language, and targeting the CH58x
MCU, smart-keymap also takes further inspiration from Kirei with its “key as the
primary keymap abstraction” idea.</p>
<p>Notice, it’s easy to consider generalisations of various smart keymap features.<br />
e.g. <a href="https://docs.qmk.fm/features/tri_layer">QMK’s tri-layer feature</a> can be
generalised as <a href="https://zmk.dev/docs/keymaps/conditional-layers">conditional
layers</a>, <a href="https://docs.qmk.fm/features/grave_esc">QMK’s
grave-esc</a> can be generalised as
<a href="https://zmk.dev/docs/keymaps/behaviors/mod-morph">mod-morph</a>. QMK’s
leader-sequences are kinda like its one-shot layers.</p>
<p>Or, generally: smart keymap functionality is about using different techniques to
modify what happens when a key is pressed &amp; released.</p>
<p><a href="https://www.reddit.com/r/ErgoMechKeyboards/comments/18wrt2i/the_layers_are_a_lie_kinda_rethinking_the/">You can even consider that “layers” fit into this
pattern</a>.</p>
<p>I thought this made for an interesting design spec: a keymap implementation
where “key behaviour” was the primary abstraction. (&amp; e.g. not “layers”).</p>
<p>Similarly, a neat observation kirei’s author made on the fak discord that a
smart keymap’s output (what keycodes get sent by a keymap, from a sequence of
key presses &amp; releases) resembles parsing a grammar. (What AST is constructed
from tokens for a grammar, from a sequence of input tokens).</p>
<h2 id="cheap-powerful-mcus-ch32x-ch58x">Cheap, Powerful MCUs: CH32X, CH58x</h2>
<p>One of the great things about the fak firmware is its support for low-cost MCUs.</p>
<p><a href="https://www.wch-ic.com/products/CH552.html">WCH’s CH552T</a> has an MCU in
TSSOP-20 for about $0.5, with 16KB flash, and about 1KB XRAM.</p>
<p>(For more GPIO / a larger package, the
<a href="https://www.wch-ic.com/products/CH559.html">CH559</a> has LQFP-48 for about $1.5,
with 60KB flash and 6KB XRAM).</p>
<p>Since smart-keymap is written in Rust, <a href="https://doc.rust-lang.org/nightly/rustc/platform-support.html">it can run on targets that Rust
supports</a>.</p>
<p>In my case, I care about CH32X and CH58x:</p>
<h3 id="mcus-ch32x">MCUs: CH32X</h3>
<p><a href="https://www.wch-ic.com/products/CH32X035.html">WCH’s CH32X</a> MCUs are about as
cheap as the CH552, but have a more powerful processor.</p>
<p>CH32X in the TSSOP-20 package costs around $0.45, its LQFP-48 package costs
0.75.<br />
The MCU has 62KB flash, 20KB SRAM.</p>
<p>The CH32X is just as simple to use in PCBs as the CH55x is.</p>
<p>The CH32X runs risc-v, and <a href="https://github.com/ch32-rs/ch32-hal/">can run Rust
firmware</a>.</p>
<h3 id="mcus-ch58x">MCUs: CH58X</h3>
<p><a href="https://www.wch-ic.com/products/CH583.html">WCH’s CH58x</a> is notable for being
relatively a BLE-capable MCU that’s relatively cheap.</p>
<p>e.g. <a href="https://aliexpress.com/item/1005004784988010.html">WeAct Studio’s CH58x core
board</a> costs about $2.5.</p>
<p>It also <a href="https://github.com/ch32-rs/ch58x-hal/">can run Rust firmware</a>.</p>
<h1 id="whats-been-done-so-far">What’s Been Done So Far</h1>
<p>Quite a bit has been done! I’ve been using keyboards running smart-keymap-based
firmware for quite some time.</p>
<h2 id="smart-key-functionality">Smart Key functionality</h2>
<p><a href="https://rgoulter.com/smart-keymap/features.html">A broad set of features have been
implemented</a>.</p>
<p>At the time of writing, the following have been implemented:</p>
<ul>
<li>basic keyboard keys,</li>
<li>sticky modifier keys,</li>
<li>caps word,</li>
<li>tap hold keys,</li>
<li>tap dance keys,</li>
<li>layered keys,</li>
<li>chorded keys.</li>
</ul>
<p>The implementation for tap-hold has some support for configuration, such as
configuring the tap-hold resolution behaviour for key interrupts, or configuring
a required idle time before a tap-hold key can resolve as its hold key.</p>
<p>(Tap-hold was surprisingly difficult to implement!)</p>
<h2 id="example-firmware">Example Firmware</h2>
<p>I’ve written some basic firmware for some of the different keyboards I’ve made:</p>
<ul>
<li>Using Rust’s <a href="https://rtic.rs/1/book/en/">RTIC</a>, firmware for <a href="https://github.com/rgoulter/smart-keymap/tree/master/rp2040-rtic-smart-keyboard">the
RP2040</a>
and
<a href="https://github.com/rgoulter/smart-keymap/tree/master/stm32f4-rtic-smart-keyboard">STM32F4</a>.</li>
<li>Using Rust’s <a href="https://embassy.dev">embassy</a>, firmware for
<a href="https://github.com/rgoulter/smart-keymap/tree/master/stm32-embassy-smart-keyboard">STM32</a>.</li>
<li>For the WCH MCUs, firmware which makes small changes to the HID keyboard
examples from the C EVT code, for
<a href="https://github.com/rgoulter/smart-keymap/tree/master/firmware/ch32x035-usb-device-compositekm-c">CH32X</a>
and for
<a href="https://github.com/rgoulter/smart-keymap/tree/master/firmware/ch58x-ble-hid-keyboard-c">CH58x</a>.</li>
</ul>
<p>The Rust implementations cover examples of col-to-row and direct pins matrices,
as well as split keyboards (both full-duplex usart, and 1-wire half-duplex
usart).</p>
<h2 id="ch32x-pcbs">CH32X PCBs</h2>
<p>Previously, I had designed a couple of keyboards which used the CH552 and fak firmware.</p>
<p>The CH32X’s TSSOP-20 pinout isn’t compatible with CH552T, but porting a PCB from
using CH552T to CH32X is nevertheless straightforward.</p>
<p>I was able to port over
<a href="https://github.com/rgoulter/keyboard-labs#ch32x-36-low-budget-36-key-split-keyboard-with-smt-components">CH32X-36</a>,
and
<a href="https://github.com/rgoulter/keyboard-labs?tab=readme-ov-file#ch32x-48-low-budget-pcba-in-bm40jj40-form-factor">CH32X-48</a>,
as well as making use of the LQFP-48 CH32X to make the
<a href="https://github.com/rgoulter/keyboard-labs?tab=readme-ov-file#ch32x-75-lpr-low-budget-pcba-with-low-profile-redragon-switches">CH32X-75</a>.</p>
<p>If you’re interested in designing a keyboard with the CH32X, the kicad sources
for these PCBs are available.</p>
<h1 id="getting-started">Getting Started</h1>
<p>If that sounds interesting enough to give a try, I’d recommend digging through
the firmware code in the
<a href="https://github.com/rgoulter/smart-keymap">smart-keymap</a> repository to figure
out how you might adapt that to your own keyboard.</p>
<p>If you happen to not have a suitable fancy custom keyboard, the
<a href="https://github.com/rgoulter/keyboard-labs#pico42-simple-hand-solderable-pcb-in-bm40jj40-form-factor">Pico42</a>
would be relatively easy to find the parts for &amp; solder. Or, the cost of getting
the PCB fabricated with PCBA for the
<a href="https://github.com/rgoulter/keyboard-labs/releases/tag/ch32x-48-rev2025.4">CH32X-48</a>
is about $5 per PCB, before S&amp;H. (You’d only need to solder the switches, which
is easy to do).</p>
<p>If you’ve already written keyboard firmware that uses keyberon, <a href="https://rgoulter.com/smart-keymap/doc/keyberon_smart_keyboard/index.html#migrating-from-keyberon">I wrote some notes on how migrating from keyberon-based code</a>.</p>]]></summary>
</entry>
<entry>
    <title>Debugging Kirei with UART</title>
    <link href="http://www.rgoulter.com/blog//posts/programming/2024-07-06-debugging-kirei-with-uart.html" />
    <id>http://www.rgoulter.com/blog//posts/programming/2024-07-06-debugging-kirei-with-uart.html</id>
    <published>2024-07-06T00:00:00Z</published>
    <updated>2024-07-06T00:00:00Z</updated>
    <summary type="html"><![CDATA[<p><a href="https://github.com/semickolon/kirei">Kirei</a> is nascent keyboard firmware, which
at the time of writing supports CH58x and RP2040 microcontrollers.<br />
It uses the same declarative approach to keyboards/keymaps that
<a href="https://github.com/semickolon/fak">fak</a> uses; albeit, Kirei’s one step further
around the <a href="https://mikehadlow.blogspot.com/2012/05/configuration-complexity-clock.html">configuration complexity
clock</a>
as it embeds a DSL into <a href="https://nickel-lang.org/">Nickel</a>.</p>
<p>CH58x (and CH592) are MCUs which are both cheap and provide BLE (Bluetooth)
functionality. For keyboard enthusiasts, they’re notable because <a href="https://github.com/WeActStudio/WeActStudio.WCH-BLE-Core">WeAct Studio
has a core board for these
MCUs</a> (for the
<a href="https://aliexpress.com/item/1005004784988010.html">CH582F</a> and
<a href="https://vi.aliexpress.com/item/1005006117859297.html">CH592F</a>).</p>
<p>(There aren’t many keyboards using these core boards; <a href="https://www.youtube.com/watch?v=mc6wTqoDZe8">but I recently recorded a demonstration of a prototype I’ve been working on</a>; <a href="https://youtu.be/5eCvUGTr0aU">including tap-hold &amp; layers functionality</a>).</p>
<p>For Kirei’s CH58x platform, <a href="https://github.com/semickolon/kirei/blob/27df23f37165d8a2b597134b32948745b8b1bee5/src/platforms/ch58x/debug.zig">it supports logging output to UART</a>.</p>
<p>Here are some notes on viewing that output on a computer:</p>
<h2 id="uart">UART</h2>
<p>I see the terms “TTL” and “RS232” mentioned alongside “UART”.</p>
<p>These StackExchange questions are relevant:</p>
<ul>
<li><a href="https://electronics.stackexchange.com/questions/274287/what-is-the-difference-between-rs-232-and-ttl-uart" class="uri">https://electronics.stackexchange.com/questions/274287/what-is-the-difference-between-rs-232-and-ttl-uart</a></li>
<li><a href="https://electronics.stackexchange.com/questions/37814/usart-uart-rs232-usb-spi-i2c-ttl-etc-what-are-all-of-these-and-how-do-th" class="uri">https://electronics.stackexchange.com/questions/37814/usart-uart-rs232-usb-spi-i2c-ttl-etc-what-are-all-of-these-and-how-do-th</a></li>
</ul>
<h2 id="connecting-to-serial-consoles">Connecting to Serial Consoles</h2>
<p>Some articles discussing how to connect to a serial console:</p>
<ul>
<li><p><a href="https://wiki.archlinux.org/title/Working_with_the_serial_console#Making_Connections">from Arch Wiki “Working with the serial console”</a></p>
<ul>
<li>I use <code>screen</code> or <code>tio</code>.</li>
</ul></li>
<li><p><a href="https://learn.adafruit.com/welcome-to-circuitpython/kattni-connecting-to-the-serial-console">from AdaFruid’s “Welcome to CircuitPython”</a></p></li>
</ul>
<h3 id="tio">TIO</h3>
<p>The Arch Wiki article mentions that <code>tio</code> is intended to replace <code>screen</code> for
this use case, for <code>tmux</code> users.</p>
<p>With <a href="https://nixos.org/">the Nix package manager</a>, getting <code>tio</code> is as simple
as <code>nix shell nixpkgs#tio</code>.</p>
<p>To connect to the serial console, the command would be something like:</p>
<pre><code>tio /dev/ttyUSB0 --baudrate 115200</code></pre>
<p>(115200 is used as a baudrate, since that’s what the <code>debug.zig</code> sets).</p>
<p>Helpfully, <code>tio</code> output includes the key sequence to quit:</p>
<pre><code>[09:22:12.178] tio v2.8
[09:22:12.178] Press ctrl-t q to quit
[09:22:12.179] Connected</code></pre>
<h3 id="screen">Screen</h3>
<p>With <a href="https://nixos.org/">the Nix package manager</a>, getting <code>screen</code> is as
simple as <code>nix shell nixpkgs#screen</code>; although I’d recommend opening a new
terminal emulator window, and not running it within tmux or
<a href="https://github.com/zellij-org/zellij/">zellij</a>.</p>
<p>To connect to the serial console, the command would be something like:</p>
<pre><code>screen /dev/ttyUSB0 115200</code></pre>
<p>(115200 is used as a baudrate, since that’s what the <code>debug.zig</code> sets).</p>
<p>Killing the session uses the sequence: <code>Ctrl-a k</code></p>
<h2 id="connecting-to-ch58xs-uart">Connecting to CH58x’s UART</h2>
<p>As the <code>debug.zig</code> code suggests, it’s using <code>A9</code> to transmit data.</p>
<h3 id="connecting-using-ftdi-ft232rl-usb-adapter">Connecting using FTDI FT232RL USB Adapter</h3>
<p>I happen to have an <a href="https://aliexpress.com/item/1005005857014294.html">FTDI FT232RL USB adapter</a>.</p>
<p>This can be used to connect a serial console to the UART output.</p>
<p>The jumper on the FTDI board should be changed to 3V3.</p>
<p>Connect a cable from the TX of the CH58x TX (A9) to the RX on the FTDI board.</p>
<p><img src="/blog/images/embedded/serial_uart_ftdi_weact_ch592.jpg" /></p>
<p>Then, with the FTDI board connected to the computer, and the CH58x board
powered, connecting to the serial console (<code>/dev/ttyUSB0</code> with baudrate 115200)
should display output from Kirei:</p>
<pre><code>$ tio /dev/ttyUSB0 --baudrate 115200
[I] Kirei 🌸
...</code></pre>
<h3 id="connecting-using-circuitpython">Connecting using CircuitPython</h3>
<p>Another way of connecting to the CH58x UART is with an Arduino board or a
CircuitPython board.</p>
<p>CircuitPython is much easier to use than Arduino.</p>
<p>I have a <a href="https://github.com/WeActStudio/WeActStudio.RP2040CoreBoard">WeAct RP2040 core board</a>
(which is similar to but not exactly a Raspberry Pi Pico).</p>
<p>CircuitPython firmware can be <a href="https://circuitpython.org/downloads">downloaded from the CircuitPython
website</a>. With the RP2040, flashing the uf2
firmware onto the board is very easy. (Connect the device with the boot switch
held, then copy the uf2 to the mass storage device).</p>
<p><a href="https://learn.adafruit.com/circuitpython-essentials/circuitpython-uart-serial">AdaFruit’s “CircuitPython Essentials” page</a>
has some code that’s close to what we want.</p>
<p>Instead of using <code>print()</code>, we’ll <a href="https://docs.circuitpython.org/en/latest/shared-bindings/usb_cdc/index.html#usb_cdc.console">write the bytes to the CDC console directly</a>.</p>
<p>Here’s the CircuitPython code for read the bytes from UART, and writing to the CDC console.</p>
<div class="sourceCode" id="cb5"><pre class="sourceCode python"><code class="sourceCode python"><span id="cb5-1"><a href="#cb5-1" aria-hidden="true" tabindex="-1"></a><span class="co">&quot;&quot;&quot;Relay UART to CDC console.&quot;&quot;&quot;</span></span>
<span id="cb5-2"><a href="#cb5-2" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> board</span>
<span id="cb5-3"><a href="#cb5-3" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> busio</span>
<span id="cb5-4"><a href="#cb5-4" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> usb_cdc</span>
<span id="cb5-5"><a href="#cb5-5" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-6"><a href="#cb5-6" aria-hidden="true" tabindex="-1"></a>uart <span class="op">=</span> busio.UART(<span class="va">None</span>, board.GP1, baudrate<span class="op">=</span><span class="dv">115200</span>)</span>
<span id="cb5-7"><a href="#cb5-7" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-8"><a href="#cb5-8" aria-hidden="true" tabindex="-1"></a><span class="cf">while</span> <span class="va">True</span>:</span>
<span id="cb5-9"><a href="#cb5-9" aria-hidden="true" tabindex="-1"></a>    data <span class="op">=</span> uart.read(<span class="dv">32</span>)</span>
<span id="cb5-10"><a href="#cb5-10" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-11"><a href="#cb5-11" aria-hidden="true" tabindex="-1"></a>    <span class="cf">if</span> data <span class="kw">is</span> <span class="kw">not</span> <span class="va">None</span>:</span>
<span id="cb5-12"><a href="#cb5-12" aria-hidden="true" tabindex="-1"></a>        usb_cdc.console.write(data)</span>
<span id="cb5-13"><a href="#cb5-13" aria-hidden="true" tabindex="-1"></a>        usb_cdc.console.flush()</span></code></pre></div>
<p>Connect a cable from the TX of the CH58x TX (A9) to the RX on the RP2040 board (GP1).</p>
<p><img src="/blog/images/embedded/serial_uart_weact_rp2040_weact_ch592.jpg" /></p>
<p>Then, with the RP2040 board connected to the computer, and the CH58x board powered,
connecting to the serial console (<code>/dev/ttyACM0</code>) should display output from Kirei:</p>
<pre><code>$ tio /dev/ttyACM0
[09:41:43.156] tio v2.8
[09:41:43.156] Press ctrl-t q to quit
[09:41:43.157] Connected
[I] Kirei 🌸
...
🐍Done | 9.0.5
Code stopped by auto-reload. Reloading soon.
soft reboot

Auto-reload is on. Simply save files over USB to run them or enter REPL to disable.
code.py output:

...</code></pre>]]></summary>
</entry>
<entry>
    <title>Indicating Success on QMK Keyboards</title>
    <link href="http://www.rgoulter.com/blog//posts/programming/2024-06-23-indicating-success-on-qmk-keyboards.html" />
    <id>http://www.rgoulter.com/blog//posts/programming/2024-06-23-indicating-success-on-qmk-keyboards.html</id>
    <published>2024-06-23T00:00:00Z</published>
    <updated>2024-06-23T00:00:00Z</updated>
    <summary type="html"><![CDATA[<p>In the previous post, I discussed ways of <a href="/blog/posts/programming/2024-06-16-using-the-qmk-leader-key-for-fancy-keyboard-functionality.html">using QMK leader key sequences in QMK keymaps</a>.</p>
<p>One of the downsides with this is that QMK leader key sequences are handled only
after a timeout (i.e. enter the key sequence, then wait a moment).</p>
<p>One way of improving the UX of this is having the keyboard provide some feedback
when a leader sequence is activated.</p>
<p>The <a href="https://docs.qmk.fm/features/leader_key">QMK docs page for the Leader key
sequences</a> gives an example using the
<code>AUDIO</code> feature, with <code>PLAY_SONG</code>, and will play song indicating whether a
leader key sequences was activated successfully or not.</p>
<p>My keyboards don’t support the <code>AUDIO</code> feature.</p>
<p>But, I do have keyboards with per-key RGB or with haptic feedback.</p>
<p>The <code>leader_end_user</code> function (which handles leader key sequences after
timeout) can be updated with:</p>
<div class="sourceCode" id="cb1"><pre class="sourceCode c"><code class="sourceCode c"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="dt">void</span> leader_end_user<span class="op">(</span><span class="dt">void</span><span class="op">)</span> <span class="op">{</span></span>
<span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a>    <span class="dt">bool</span> did_leader_succeed <span class="op">=</span> <span class="kw">true</span><span class="op">;</span></span>
<span id="cb1-3"><a href="#cb1-3" aria-hidden="true" tabindex="-1"></a>    <span class="cf">if</span> <span class="op">(</span>leader_sequence_two_keys<span class="op">(</span>KC_Q<span class="op">,</span> KC_B<span class="op">))</span> <span class="op">{</span> <span class="co">// mnemonic: QMK Boot</span></span>
<span id="cb1-4"><a href="#cb1-4" aria-hidden="true" tabindex="-1"></a>        reset_keyboard<span class="op">();</span></span>
<span id="cb1-5"><a href="#cb1-5" aria-hidden="true" tabindex="-1"></a>    <span class="op">}</span> <span class="cf">else</span> <span class="cf">if</span> <span class="op">(</span><span class="co">/* ... */</span><span class="op">)</span> <span class="op">{</span></span>
<span id="cb1-6"><a href="#cb1-6" aria-hidden="true" tabindex="-1"></a>        <span class="co">/* .... */</span></span>
<span id="cb1-7"><a href="#cb1-7" aria-hidden="true" tabindex="-1"></a>    <span class="op">}</span> <span class="cf">else</span> <span class="op">{</span></span>
<span id="cb1-8"><a href="#cb1-8" aria-hidden="true" tabindex="-1"></a>        did_leader_succeed <span class="op">=</span> <span class="kw">false</span><span class="op">;</span></span>
<span id="cb1-9"><a href="#cb1-9" aria-hidden="true" tabindex="-1"></a>    <span class="op">}</span></span>
<span id="cb1-10"><a href="#cb1-10" aria-hidden="true" tabindex="-1"></a>    leader_end_notify<span class="op">(</span>did_leader_succeed<span class="op">);</span></span>
<span id="cb1-11"><a href="#cb1-11" aria-hidden="true" tabindex="-1"></a><span class="op">}</span></span></code></pre></div>
<p>i.e. call some <code>leader_end_notify</code> function, unless the leader key sequence
wasn’t recognised. (It’s conceptually neater to initialize <code>did_leader_succeed</code> with
<code>false</code>, and only set it to <code>true</code> when handling a key sequence.. but, that
would take more lines of code for the same effect).</p>
<p>The <code>leader_end_notify</code> can be written like this:</p>
<div class="sourceCode" id="cb2"><pre class="sourceCode c"><code class="sourceCode c"><span id="cb2-1"><a href="#cb2-1" aria-hidden="true" tabindex="-1"></a><span class="dt">void</span> leader_end_notify<span class="op">(</span><span class="dt">bool</span> succeeded<span class="op">)</span> <span class="op">{</span></span>
<span id="cb2-2"><a href="#cb2-2" aria-hidden="true" tabindex="-1"></a><span class="pp">#    ifdef HAPTIC_ENABLE</span></span>
<span id="cb2-3"><a href="#cb2-3" aria-hidden="true" tabindex="-1"></a>    <span class="cf">if</span> <span class="op">(</span>succeeded <span class="op">&amp;&amp;</span> haptic_get_enable<span class="op">())</span> <span class="op">{</span></span>
<span id="cb2-4"><a href="#cb2-4" aria-hidden="true" tabindex="-1"></a>        haptic_play<span class="op">();</span></span>
<span id="cb2-5"><a href="#cb2-5" aria-hidden="true" tabindex="-1"></a>    <span class="op">}</span></span>
<span id="cb2-6"><a href="#cb2-6" aria-hidden="true" tabindex="-1"></a><span class="pp">#    endif</span></span>
<span id="cb2-7"><a href="#cb2-7" aria-hidden="true" tabindex="-1"></a><span class="pp">#    ifdef RGB_MATRIX_ENABLE</span></span>
<span id="cb2-8"><a href="#cb2-8" aria-hidden="true" tabindex="-1"></a>    rgb_matrix_blink_start<span class="op">(</span>succeeded<span class="op">);</span></span>
<span id="cb2-9"><a href="#cb2-9" aria-hidden="true" tabindex="-1"></a><span class="pp">#    endif</span></span>
<span id="cb2-10"><a href="#cb2-10" aria-hidden="true" tabindex="-1"></a><span class="op">}</span></span></code></pre></div>
<p>The <code>rgb_matrix_blink_start</code> is a function can is a bit trickier.</p>
<p>On a split keyboard, it’s a bit difficult to communicate state between the
master half and the slave half. – My first attempt, I tried using the
<code>rgb_matrix_set_color_all</code> function; but, this didn’t update the RGB LEDs
on the slave half.</p>
<p>I work with this limitation by using the RGB matrix effects. The QMK framework’s
split keyboard transport protocol already handles syncing state for RGB effects.</p>
<p>So, we “blink” the keyboard by:</p>
<ul>
<li>setting the effect to “solid color” (white).</li>
<li>waiting a bit.</li>
<li>restoring the effect/color to what it was before.</li>
</ul>
<p>Hence, the implmentation, we have some extra state to store:</p>
<div class="sourceCode" id="cb3"><pre class="sourceCode c"><code class="sourceCode c"><span id="cb3-1"><a href="#cb3-1" aria-hidden="true" tabindex="-1"></a><span class="pp">#ifdef RGB_MATRIX_ENABLE</span></span>
<span id="cb3-2"><a href="#cb3-2" aria-hidden="true" tabindex="-1"></a><span class="dt">uint8_t</span>  old_mode               <span class="op">=</span> <span class="dv">0</span><span class="op">;</span></span>
<span id="cb3-3"><a href="#cb3-3" aria-hidden="true" tabindex="-1"></a>HSV      old_hsv                <span class="op">=</span> <span class="op">{</span>HSV_OFF<span class="op">};</span></span>
<span id="cb3-4"><a href="#cb3-4" aria-hidden="true" tabindex="-1"></a><span class="dt">uint32_t</span> rgb_matrix_blink_timer <span class="op">=</span> <span class="dv">0</span><span class="op">;</span></span>
<span id="cb3-5"><a href="#cb3-5" aria-hidden="true" tabindex="-1"></a><span class="dt">bool</span>     blinking_active        <span class="op">=</span> <span class="kw">false</span><span class="op">;</span></span>
<span id="cb3-6"><a href="#cb3-6" aria-hidden="true" tabindex="-1"></a><span class="pp">#endif</span></span></code></pre></div>
<p>In <code>rgb_matrix_blink_start</code>, we store the current RGB effect and colour, and keep track of the time:</p>
<div class="sourceCode" id="cb4"><pre class="sourceCode c"><code class="sourceCode c"><span id="cb4-1"><a href="#cb4-1" aria-hidden="true" tabindex="-1"></a><span class="dt">void</span> rgb_matrix_blink_start<span class="op">(</span><span class="dt">bool</span> succeeded<span class="op">)</span> <span class="op">{</span></span>
<span id="cb4-2"><a href="#cb4-2" aria-hidden="true" tabindex="-1"></a>    <span class="co">/* impl. note: in order to get the &#39;blink&#39; to work across split keyboard,</span></span>
<span id="cb4-3"><a href="#cb4-3" aria-hidden="true" tabindex="-1"></a><span class="co">     *  save the old mode &amp; hsv, then set to solid white.</span></span>
<span id="cb4-4"><a href="#cb4-4" aria-hidden="true" tabindex="-1"></a><span class="co">     * The old mode &amp; hsv are then restored after some time,</span></span>
<span id="cb4-5"><a href="#cb4-5" aria-hidden="true" tabindex="-1"></a><span class="co">     *  in the rgb_matrix_indicators_user callback.</span></span>
<span id="cb4-6"><a href="#cb4-6" aria-hidden="true" tabindex="-1"></a><span class="co">     */</span></span>
<span id="cb4-7"><a href="#cb4-7" aria-hidden="true" tabindex="-1"></a>    old_mode <span class="op">=</span> rgb_matrix_get_mode<span class="op">();</span></span>
<span id="cb4-8"><a href="#cb4-8" aria-hidden="true" tabindex="-1"></a>    old_hsv  <span class="op">=</span> rgb_matrix_get_hsv<span class="op">();</span></span>
<span id="cb4-9"><a href="#cb4-9" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-10"><a href="#cb4-10" aria-hidden="true" tabindex="-1"></a>    rgb_matrix_mode_noeeprom<span class="op">(</span>RGB_MATRIX_SOLID_COLOR<span class="op">);</span></span>
<span id="cb4-11"><a href="#cb4-11" aria-hidden="true" tabindex="-1"></a>    <span class="cf">if</span> <span class="op">(</span>succeeded<span class="op">)</span> <span class="op">{</span></span>
<span id="cb4-12"><a href="#cb4-12" aria-hidden="true" tabindex="-1"></a>        rgb_matrix_sethsv_noeeprom<span class="op">(</span>HSV_WHITE<span class="op">);</span></span>
<span id="cb4-13"><a href="#cb4-13" aria-hidden="true" tabindex="-1"></a>    <span class="op">}</span> <span class="cf">else</span> <span class="op">{</span></span>
<span id="cb4-14"><a href="#cb4-14" aria-hidden="true" tabindex="-1"></a>        rgb_matrix_sethsv_noeeprom<span class="op">(</span>HSV_RED<span class="op">);</span></span>
<span id="cb4-15"><a href="#cb4-15" aria-hidden="true" tabindex="-1"></a>    <span class="op">}</span></span>
<span id="cb4-16"><a href="#cb4-16" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-17"><a href="#cb4-17" aria-hidden="true" tabindex="-1"></a>    rgb_matrix_blink_timer <span class="op">=</span> timer_read32<span class="op">();</span></span>
<span id="cb4-18"><a href="#cb4-18" aria-hidden="true" tabindex="-1"></a>    blinking_active        <span class="op">=</span> <span class="kw">true</span><span class="op">;</span></span>
<span id="cb4-19"><a href="#cb4-19" aria-hidden="true" tabindex="-1"></a><span class="op">}</span></span>
<span id="cb4-20"><a href="#cb4-20" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-21"><a href="#cb4-21" aria-hidden="true" tabindex="-1"></a><span class="dt">void</span> rgb_matrix_blink_end<span class="op">(</span><span class="dt">void</span><span class="op">)</span> <span class="op">{</span></span>
<span id="cb4-22"><a href="#cb4-22" aria-hidden="true" tabindex="-1"></a>    rgb_matrix_mode_noeeprom<span class="op">(</span>old_mode<span class="op">);</span></span>
<span id="cb4-23"><a href="#cb4-23" aria-hidden="true" tabindex="-1"></a>    rgb_matrix_sethsv_noeeprom<span class="op">(</span>old_hsv<span class="op">.</span>h<span class="op">,</span> old_hsv<span class="op">.</span>s<span class="op">,</span> old_hsv<span class="op">.</span>v<span class="op">);</span></span>
<span id="cb4-24"><a href="#cb4-24" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-25"><a href="#cb4-25" aria-hidden="true" tabindex="-1"></a>    blinking_active <span class="op">=</span> <span class="kw">false</span><span class="op">;</span></span>
<span id="cb4-26"><a href="#cb4-26" aria-hidden="true" tabindex="-1"></a><span class="op">}</span></span></code></pre></div>
<p>and in <code>rgb_matrix_blink_end</code>, we restore the RGB effect and colour.</p>
<p>This <code>rgb_matrix_blink_end</code> is called by a “housekeeping task”, which is called by QMK at the end of each scan loop.
It simply checks the blink timer, and calls the blink end function if enough time has passed.</p>
<div class="sourceCode" id="cb5"><pre class="sourceCode c"><code class="sourceCode c"><span id="cb5-1"><a href="#cb5-1" aria-hidden="true" tabindex="-1"></a><span class="dt">void</span> housekeeping_task_user<span class="op">(</span><span class="dt">void</span><span class="op">)</span> <span class="op">{</span></span>
<span id="cb5-2"><a href="#cb5-2" aria-hidden="true" tabindex="-1"></a>    <span class="cf">if</span> <span class="op">(</span>blinking_active <span class="op">&amp;&amp;</span> timer_elapsed32<span class="op">(</span>rgb_matrix_blink_timer<span class="op">)</span> <span class="op">&gt;=</span> RGB_MATRIX_BLINK_INTERVAL<span class="op">)</span> <span class="op">{</span></span>
<span id="cb5-3"><a href="#cb5-3" aria-hidden="true" tabindex="-1"></a>        rgb_matrix_blink_end<span class="op">();</span></span>
<span id="cb5-4"><a href="#cb5-4" aria-hidden="true" tabindex="-1"></a>    <span class="op">}</span></span>
<span id="cb5-5"><a href="#cb5-5" aria-hidden="true" tabindex="-1"></a><span class="op">}</span></span></code></pre></div>
<p>With this, I can now confidently be notified that a leader sequence succeeded,
using either haptic feedback, or RGB matrix functionality.</p>
<p>I expect that code for the <code>RGBLIGHT</code> QMK feature would be similar to the RGB matrix implementation above.</p>
<p>So, e.g., if I use a leader key sequence to enable caps word, I can be confident
caps word has activated. Or if I invoke a sequence and don’t see the keyboard
‘blink’ (or don’t receive haptic feedback), then I can understand that I failed
to enter a leader key sequence.</p>]]></summary>
</entry>

</feed>
