<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:atom="http://www.w3.org/2005/Atom" version="2.0">
    <channel>
      <title>gridbugs</title>
      <link>https://www.gridbugs.org</link>
      <description></description>
      <generator>Zola</generator>
      <language>en</language>
      <atom:link href="https://www.gridbugs.org/rss.xml" rel="self" type="application/rss+xml"/>
      <lastBuildDate>Fri, 06 Mar 2026 00:00:00 +0000</lastBuildDate>
      <item>
          <title>7 Day Roguelike 2026: Day 7</title>
          <pubDate>Fri, 06 Mar 2026 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/7drl2026-day7/</link>
          <guid>https://www.gridbugs.org/7drl2026-day7/</guid>
          <description xml:base="https://www.gridbugs.org/7drl2026-day7/">&lt;p&gt;Tonight I finished the game and &lt;a href=&quot;https:&#x2F;&#x2F;gridbugs.itch.io&#x2F;road-closed&quot;&gt;released&lt;&#x2F;a&gt; it on itch.io.
I spent the night working on populating maps, adding a few new enemy types, and then playtesting, balancing, and fixing bugs.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;7drl2026-day7&#x2F;screenshot.jpg&quot; alt=&quot;&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;The result is playable and there’s a steady difficulty curve requiring players to make decisions about whether to fight
or run away from an encounter, and whether they’ve scavenged enough in one area and should return to the car.
The presence of the car means that you can always escape an encounter if you can make it back to the car in one piece.
Most enemies can be fought on their own fairly easily but taking on more than one at a
time is dangerous. Character progression is a mix of finding better equipment and hoarding resources to use in emergencies.&lt;&#x2F;p&gt;
&lt;p&gt;I knew I was going to have a busy week during the 7DRL challenge week so the scope of Road Closed is deliberately quite limited.
I would have liked to add an upgrade system based on equipping mysterious artifacts like in the S.T.A.L.K.E.R franchise, or
add events that take place in the game’s driving mode similar in FTL, but I didn’t have time.&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>7 Day Roguelike 2026: Day 6</title>
          <pubDate>Thu, 05 Mar 2026 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/7drl2026-day6/</link>
          <guid>https://www.gridbugs.org/7drl2026-day6/</guid>
          <description xml:base="https://www.gridbugs.org/7drl2026-day6/">&lt;p&gt;Tonight I added some more terrain generators. There are now three different
types of terrain, and as you drive the current terrain periodically changes.
The two new types of terrain are a swamp and mountain pass. All three terrain
generators are wilderness areas. I was originally going to have a mix of
wilderness and urban areas but I’ve always found it hard to implement
procedural generation of urban areas and I don’t think there’s enough time left
in the jam to get it right.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;7drl2026-day6&#x2F;screenshot.jpg&quot; alt=&quot;&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;The main task I still need to do is populating the terrain with enemies and items.
Tomorrow is the final day of the 7DRL and my plan is to populate the maps and playtest
to balance them. I should also probably add a couple more types of enemy as
currently there are only three.&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>7 Day Roguelike 2026: Day 5</title>
          <pubDate>Wed, 04 Mar 2026 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/7drl2026-day5/</link>
          <guid>https://www.gridbugs.org/7drl2026-day5/</guid>
          <description xml:base="https://www.gridbugs.org/7drl2026-day5/">&lt;p&gt;A bunch of content additions tonight. I added a variety of melee weapons and
armour that interact with the games existing systems. Heavier armour requires
you to eat more food to support the extra weight of the armour. There’s a high
damage axe which costs energy to use.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;7drl2026-day5&#x2F;screenshot.jpg&quot; alt=&quot;&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;I also added a slime enemy that splits when attacked.&lt;&#x2F;p&gt;
&lt;p&gt;The plan for tomorrow is to add a new terrain generator (probably a city) and
populate the generated terrain with items and enemies. This should be enough to
get the game in a playable state, and then I’ll have one more night to add more
content (hopefully a third terrain generator), polish, playtest, and balance.&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>7 Day Roguelike 2026: Day 4</title>
          <pubDate>Tue, 03 Mar 2026 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/7drl2026-day4/</link>
          <guid>https://www.gridbugs.org/7drl2026-day4/</guid>
          <description xml:base="https://www.gridbugs.org/7drl2026-day4/">&lt;p&gt;Today I added the remaining distance and current day to the UI, and added a simple equipment system.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;7drl2026-day4&#x2F;screenshot.jpg&quot; alt=&quot;&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>7 Day Roguelike 2026: Day 3</title>
          <pubDate>Mon, 02 Mar 2026 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/7drl2026-day3/</link>
          <guid>https://www.gridbugs.org/7drl2026-day3/</guid>
          <description xml:base="https://www.gridbugs.org/7drl2026-day3/">&lt;p&gt;Today I added a win condition, which is reaching the room containing the “wish granter”, borrowing some lore from Roadside Picnic
which Pacific Drive was loosely based on. After driving sufficiently far the game generates the final level.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;7drl2026-day3&#x2F;screenshot.jpg&quot; alt=&quot;&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;I still need to
add some indication of the distance remaining to the game’s UI and add some variety to the terrain that gets generated
for regular gameplay.&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>7 Day Roguelike 2026: Day 2</title>
          <pubDate>Sun, 01 Mar 2026 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/7drl2026-day2/</link>
          <guid>https://www.gridbugs.org/7drl2026-day2/</guid>
          <description xml:base="https://www.gridbugs.org/7drl2026-day2/">&lt;p&gt;Today I added a terrain generator for a forest biome, implemented the game’s
basic systems, added a few hostile NPCs, a car inventory where items can be
exchanged with the player’s inventory, and items for recovering health, energy,
food, and fuel.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;7drl2026-day2&#x2F;screenshot.jpg&quot; alt=&quot;&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;I still need to add NPCs and items to the generated levels and add a win condition. I’ll do both of those tomorrow.&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>7 Day Roguelike 2026: Day 1</title>
          <pubDate>Sat, 28 Feb 2026 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/7drl2026-day1/</link>
          <guid>https://www.gridbugs.org/7drl2026-day1/</guid>
          <description xml:base="https://www.gridbugs.org/7drl2026-day1/">&lt;p&gt;This year my game will be about taking a road trip through a mysterious exclusion zone,
kind of like Pacific Drive, only it’s one continuous journey rather than a series of short
runs. And obviously it’s an ASCII turn-based traditional roguelike.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;7drl2026-day1&#x2F;screenshot2.jpg&quot; alt=&quot;&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;I spent today dusting off my codebase from my submission from 2 years ago (&lt;a href=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;electric-organ&#x2F;&quot;&gt;Electric Organ&lt;&#x2F;a&gt;), reworking the UI, and
adding a driving mode pictured above. The gameplay loop will alternate between two modes:
a driving mode where you choose where and when to stop and &lt;em&gt;hopefully&lt;&#x2F;em&gt; make some
decisions about which routes to take and deal with random events (though these
are all stretch goals) and a regular walking around mode, scavenging for resources to keep the journey going.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;7drl2026-day1&#x2F;screenshot1.jpg&quot; alt=&quot;&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;I don’t have as much free time to devote to 7DRL this year. I’m going to try to get a basic framework
up and running tomorrow for procgen, implement the systems that drive the
status bars at the bottom of the screen, and add a win condition. Then the
remaining work will mostly be adding content, which I can add as much or as
little of as I have time to do so.&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Dial-Up over VoIP with a Commodore Modem from 1985</title>
          <pubDate>Fri, 27 Feb 2026 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/dial-up-over-voip-with-a-commodore-modem-from-1985/</link>
          <guid>https://www.gridbugs.org/dial-up-over-voip-with-a-commodore-modem-from-1985/</guid>
          <description xml:base="https://www.gridbugs.org/dial-up-over-voip-with-a-commodore-modem-from-1985/">&lt;p&gt;I connected a pair of computers with a 300 bits&#x2F;s point-to-point dial-up
network through a pair of Voice-over-IP (VoIP) phone lines. This type of
networking predates commercial dial-up internet, and required users to directly
call the specific online service they wished to connect to, such as CompuServe
or a bulletin board system. Nowadays there’s little practical benefit to
dial-up networking. It’s also become more difficult over time because telcos
have started digitizing landlines employing codecs and audio filtering
optimized for voice that can interfere with the tones that dial-up modems use
to communicate. VoIP phone lines have been known to suffer from similar issues,
but at least give users some amount of control over how the signal is digitized.&lt;&#x2F;p&gt;
&lt;p&gt;Despite this I thought it would be really cool to connect my Commodore 64 to
some dial-up BBSes, but there don’t seem to be any left in Australia.
So then I thought it would be even cooler to run my own.
I’m not at that point yet, but getting two computers to communicate with each
other at all was very involved so I’m documenting what I’ve found out so far
before going further. Two months ago I’d never heard of an ATA or
Hayes-compatible modem so telephony-wise I was really starting from scratch. This
post is aimed at me two months ago and anyone else curious about going online
like it’s 1985 but aren’t sure how to start.&lt;&#x2F;p&gt;
&lt;p&gt;Here’s my setup:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;dial-up-over-voip-with-a-commodore-modem-from-1985&#x2F;setup.jpg&quot; alt=&quot;Desk with a pair of computers. On the left is a Commodore 64 and on the right is a PC. Between them is an analog telephone adapter and dial-up modem&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;On the left is a Commodore 64 with a Commodore 300 Modem attached.
On the right is a regular PC running Linux. In between, the beige box
is a dial-up modem connected to the PC’s serial port, and the black
box is an analog telephone adapter (ATA) which converts analog telephone
signals to VoIP. Both modems are attached to the ATA with phone cables.&lt;&#x2F;p&gt;
&lt;p&gt;Here’s a diagram showing all the devices involved and how they’re connected to each other.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;dial-up-over-voip-with-a-commodore-modem-from-1985&#x2F;topology.jpg&quot; alt=&quot;Topology of dial-up setup&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;I can call the PC’s modem from the Commodore 64 and answer the call on the PC side.
The modems handshake with a variant of the iconic “dial-up modem” sound.
Then they are connected, and when I type
on the Commodore the text appears on the PC’s terminal and vice versa.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;dial-up-mental-model&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#dial-up-mental-model&quot; aria-label=&quot;Anchor link for: dial-up-mental-model&quot;&gt;Dial-up Mental Model&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;Each computer is connected to
a modem via a serial interface. There’s a wire for sending data and a separate wire for
receiving data, plus some additional wires for control signals and ground.
At any point in time, each data wire can either be a (relatively) high voltage (e.g. 5v) representing a binary 1
or a low voltage (e.g. 0v) representing 0, and the sender sends a single bit at a time by setting
the voltage of the sending wire. There is no clock signal, so both the computer and modem
must agree on the bit rate to effectively interpret each other’s signals.
There’s no out of band way to indicate that data is currently being sent.
Instead, the neutral state of a data wire is high voltage, and data is sent in blocks of (usually 8) bits
preceded by a single 0 bit called the “start bit”, and followed by one or two 1 bits called “stop bits”,
and the wire is left at high voltage until the next start bit.
To send a character over serial, such as when you press a key on your keyboard attached to a serial terminal,
the character is numerically encoded (e.g. with ASCII) and the encoded value is sent least-significant-bit-first
wrapped in start and stop bits. When data is received by the serial port, the terminal decodes the value
and prints the character it represents.&lt;&#x2F;p&gt;
&lt;p&gt;Many dial-up modems implement a set of commands known as the &lt;a href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;Hayes_AT_command_set&quot;&gt;Hayes AT command
set&lt;&#x2F;a&gt;, where the text of
commands and their responses are sent over the serial interface between the
computer and modem. These commands can be used to configure the modem or take actions such as
dialing a number or answering a call.
I mention this here as an example of the computer communicating with the modem directly rather than as
a conduit to connect with a remote computer.&lt;&#x2F;p&gt;
&lt;p&gt;When one modem is connected to another modem through the phone network, data received by a modem from
its local computer is sent over the phone line by modulating the frequency of a “carrier signal”, a method
known as &lt;a href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;Frequency-shift_keying&quot;&gt;Frequency-shift Keying (FSK)&lt;&#x2F;a&gt;.
If you could listen to the analog signal sent or received over the phone line by a modem you would hear a faint
tone in the order of 1 to 2 kHz. This is the carrier signal.
Both modems generate a carrier signal which they modulate to send data to the other modem.
The specific way the carrier signal is
modulated depends on the speed of the connection between the modems. In my setup they send 300 bits&#x2F;s
using a protocol called &lt;a href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;Bell_103&quot;&gt;Bell 103&lt;&#x2F;a&gt;, where the initial carrier
signal represents 1, and its pitch shifted down by ~200Hz to send a 0.&lt;&#x2F;p&gt;
&lt;p&gt;Here’s a recording I took of the audio received by one of my modems over the phone line while some
data was being transmitted. The X-axis is time and the Y-axis is pitch, and the pitches representing
a 0 and 1 are clearly visible.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;dial-up-over-voip-with-a-commodore-modem-from-1985&#x2F;modem-recording.jpg&quot; alt=&quot;A horizontal green line varying between two different heights, visualizing a binary signal&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;As with a serial interface
there is no clock signal, so both modems have to agree on a bit rate.
There are multiple different FSK modem protocols, and each prescribes the carrier frequencies
and their modulation, and the speed of the connection. The modems negotiate which protocol they
will use during the modem handshake when a phone call between modems initially connects.&lt;&#x2F;p&gt;
&lt;p&gt;If analog landlines were still common the above explanation would be sufficient,
but to connect two modems over the phone network in 2026 you’re probably going to
need to do so over the internet using VoIP. To use an analog device like a modem or analog phone
you’ll need an analog telephone adapter (ATA), which is a device with phone jacks and usually
an Ethernet port so it can connect to the internet via your router. You’ll also need to make
an account with a VoIP provider and rent some phone numbers from them, and configure your ATA
so that each phone number is associated with one of its phone jacks. You’ll also need to disable
echo cancellation in your ATA’s settings or it messes with the modem signals.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;analog-telephone-adapter-ata&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#analog-telephone-adapter-ata&quot; aria-label=&quot;Anchor link for: analog-telephone-adapter-ata&quot;&gt;Analog Telephone Adapter (ATA)&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;An ATA is a device which converts analog phone signals to VoIP.
I’m using a Grandstream HT802 v2 as my ATA which has two telephone ports
allowing me to plug in both modems. I made an account with a VoIP provider
(Crazytel), set up two SIPs, each with their own associated phone number, and
then registered one SIP for each port of the ATA via its web interface.&lt;&#x2F;p&gt;
&lt;p&gt;Dial-up networking over VoIP requires as raw a signal as possible being sent between the modems.
Make sure the first choice of codec is PCMU, also known as G.711, which is the highest quality
codec available on most ATAs. Also make sure any echo or silence suppression provided by the ATA
is disabled. Enable jitter buffering (it’s probably enabled by default). I found it’s most reliable
to use the maximum amount of jitter buffering available on the ATA.&lt;&#x2F;p&gt;
&lt;p&gt;Test the ATA setup by making calls between each VoIP phone number and your mobile phone by plugging
an analog phone into each port on the ATA.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;pc-and-dial-up-modem&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#pc-and-dial-up-modem&quot; aria-label=&quot;Anchor link for: pc-and-dial-up-modem&quot;&gt;PC and dial-up modem&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;My modem is a NetComm Roadster II 56 Ultra SVD. There’s not much information about it online and I
wasn’t able to dig up a manual, but there are some photos &lt;a href=&quot;https:&#x2F;&#x2F;wiki.preterhuman.net&#x2F;NetComm_Roadster_II_56_Ultra_SVD&quot;&gt;here&lt;&#x2F;a&gt; and it’s also mentioned &lt;a href=&quot;https:&#x2F;&#x2F;goughlui.com&#x2F;2012&#x2F;11&#x2F;27&#x2F;tech-flashback-other-external-modems-in-my-collection&#x2F;&quot;&gt;here&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Here it is. The lights on the front are labeled: Power, Terminal Ready, Off Hook, Carrier Detect, Data.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;dial-up-over-voip-with-a-commodore-modem-from-1985&#x2F;netcomm-modem-iso.jpg&quot; alt=&quot;Netcomm Roadster II 56 Ultra modem, isometric view&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Here’s the back. The “Data” port is how it connects to the PC.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;dial-up-over-voip-with-a-commodore-modem-from-1985&#x2F;netcomm-modem-back.jpg&quot; alt=&quot;Netcomm Roadster II 56 Ultra modem, rear view showing jacks: Power, Data, Phone, Line&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;You’ll need a DB9 male to female serial cable like this one to connect it to a PC:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;dial-up-over-voip-with-a-commodore-modem-from-1985&#x2F;serial-cable.jpg&quot; alt=&quot;The ends of a DB9 M&#x2F;F serial cable&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;I was lucky enough to find a computer abandoned under a bridge which is old enough to still have a
DB9 port but new enough to run a modern OS. Most computers from the past decade or so don’t have this port
but it should be easy to find a USB to DB9 adapter.&lt;&#x2F;p&gt;
&lt;p&gt;To interact with the modem from the PC you’ll need terminal software that can send and receive data
over a serial port. On Linux I usually use &lt;code&gt;picocom&lt;&#x2F;code&gt;. Once everything is connected and powered on, run
&lt;code&gt;picocom &#x2F;dev&#x2F;ttyS0&lt;&#x2F;code&gt; from a terminal.
This should cause the “Terminal Ready” light on the modem to light up.
If it doesn’t, your serial cable is bad, and while you’ll still be able to interact with the modem directly
over serial port, you won’t be able to send or receive any data from the remote side once a connection is
established. Ask me how I know.&lt;&#x2F;p&gt;
&lt;p&gt;Typing in a picocom session sends characters over the serial connection.
Eventually we’ll use this to send characters all the way to the Commodore 64 via the dial-up network, but
for now the characters will be interpreted by the modem, which implements the
&lt;a href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;Hayes_AT_command_set&quot;&gt;Hayes AT command set&lt;&#x2F;a&gt;.
These are commands for configuring a modem or performing actions such as
dialing a number or answering a call.&lt;&#x2F;p&gt;
&lt;p&gt;To test that the modem is working, try typing &lt;code&gt;AT&lt;&#x2F;code&gt; (then hit enter), to which the modem should reply &lt;code&gt;OK&lt;&#x2F;code&gt;.
Test that the modem can receive calls by connecting it to one of the ATA’s phone ports with a phone cable
and then calling number associated with that port (with your mobile phone).
When I do this the modem sends the word &lt;code&gt;RING&lt;&#x2F;code&gt; over the serial connection. The
command to answer a call is &lt;code&gt;ATA&lt;&#x2F;code&gt; (it’s a coincidence that this is the name for
the device for connecting analog devices to VoIP). The modem will probably send
some beeps over the phone connection then give up when it doesn’t receive a
carrier signal. The command to end a call is &lt;code&gt;ATH&lt;&#x2F;code&gt;. It’s not directly relevant to this setup,
but you can also dial a number from the modem with the command &lt;code&gt;ATDT&lt;&#x2F;code&gt; followed by the number.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;commodore-64-and-commodore-modem-300&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#commodore-64-and-commodore-modem-300&quot; aria-label=&quot;Anchor link for: commodore-64-and-commodore-modem-300&quot;&gt;Commodore 64 and Commodore Modem 300&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;Here’s my Commodore 64:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;dial-up-over-voip-with-a-commodore-modem-from-1985&#x2F;c64-top.jpg&quot; alt=&quot;Commodore 64 top view showing RCA adapter and SD2IEC peripherals&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;I’m using a &lt;a href=&quot;https:&#x2F;&#x2F;www.c64-wiki.com&#x2F;wiki&#x2F;SD2IEC&quot;&gt;SD2IEC&lt;&#x2F;a&gt; device (the black
box at the top right) to run programs from an SD card.&lt;&#x2F;p&gt;
&lt;p&gt;The Commodore Modem slots into the Commodore 64’s user port. Here’s the modem:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;dial-up-over-voip-with-a-commodore-modem-from-1985&#x2F;commodore-modem-front.jpg&quot; alt=&quot;Commodore Modem 300 front view showing user port connector&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;This modem doesn’t have built-in support for generating the &lt;a href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;DTMF_signaling&quot;&gt;DTMF touch tones&lt;&#x2F;a&gt; to dial a number.
Instead, there’s an “Audio In” RCA jack on the back which you can play touch tones into.
The intended usage was to connect the audio signal coming out of the Commodore 64 itself
to this jack and then use the Commodore 64’s sound chip to generate touch tones.
The terminal software I’m using on the Commodore doesn’t have support for generating these tones
however.&lt;&#x2F;p&gt;
&lt;p&gt;Instead, I found two ways to initialize a call:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Generate touch tones on a laptop or mobile phone, with its headphone jack
attached to the modem’s “Audio In” with a 3.5mm to RCA adapter (using either
the red or white RCA cable - it doesn’t matter which). I used
&lt;a href=&quot;https:&#x2F;&#x2F;apkpure.com&#x2F;dtmf-tone-generator-decoder&#x2F;com.arcoirislabs.dtmfencdec.app&quot;&gt;this&lt;&#x2F;a&gt;
android app to generate the tones.&lt;&#x2F;li&gt;
&lt;li&gt;Plug an analog phone into the “Phone” jack on the back of the modem, and use the phone to initialize the call.
Then use terminal software on the Commodore 64 to have the modem take over the call before the other side picks up.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Read more about initiating a call &lt;a href=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;dial-up-over-voip-with-a-commodore-modem-from-1985&#x2F;#initiating-a-call&quot;&gt;below&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Here’s the back of the modem showing the “Audio In” and “Phone” jacks, as well as the “Line” jack which will be used to connect the modem to the ATA.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;dial-up-over-voip-with-a-commodore-modem-from-1985&#x2F;commodore-modem-back.jpg&quot; alt=&quot;Commodore Modem 300 rear view showing Audio In, Phone, and Line jacks&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Also in the above image are the two phone jacks. Connect the one marked “Line” to the ATA, and plug an analog
phone into the one marked “Phone”. The “Phone” jack is probably so that households with a single landline could
share it between the modem and phone without needing to physically unplug the phone and plug in the modem every
time they wanted to use the modem. In this setup the analog phone is used for initializing a call to the PC’s modem,
and also for resetting the modem at the end of a call.&lt;&#x2F;p&gt;
&lt;p&gt;The modem has a built-in speaker which lets you hear the dial tone and other phone noises to understand
what state the line is in. Unfortunately it’s really loud and of poor quality, and there’s no way to turn
it down or off. After a day of this I decided to make some modifications and added this headphone jack
to the side, and now I can plug in a speaker with a volume knob. The audio
quality was also dramatically improved. This headphone jack also makes it possible to record the raw
audio signal received by the modem, which I wasn’t able to do before.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;dial-up-over-voip-with-a-commodore-modem-from-1985&#x2F;commodore-modem-headphones-jack.jpg&quot; alt=&quot;Commodore Modem 300 side view showing custom headphone jack&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;The terminal program I’m using on the Commodore is &lt;a href=&quot;https:&#x2F;&#x2F;www.c64-wiki.com&#x2F;wiki&#x2F;Novaterm&quot;&gt;Novaterm&lt;&#x2F;a&gt;
which I’m loading off an SD card with the SD2IEC device.
Here’s the main menu:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;dial-up-over-voip-with-a-commodore-modem-from-1985&#x2F;novaterm1.jpg&quot; alt=&quot;Main menu of Novaterm program&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;The configuration to make Novaterm work with the Commodore modem is below.
On the first configuration page, the &lt;code&gt;Modem type&lt;&#x2F;code&gt; must be set to &lt;code&gt;1660&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;dial-up-over-voip-with-a-commodore-modem-from-1985&#x2F;novaterm2.jpg&quot; alt=&quot;First configuration page of Novaterm program&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;On the second configuration page, the &lt;code&gt;Serial driver&lt;&#x2F;code&gt; must be &lt;code&gt;User port&lt;&#x2F;code&gt;.
Also enable &lt;code&gt;Print in ASCII&lt;&#x2F;code&gt; since the Linux side of the connection will assume text is ASCII-encoded.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;dial-up-over-voip-with-a-commodore-modem-from-1985&#x2F;novaterm3.jpg&quot; alt=&quot;Second configuration page of Novaterm program&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Once Novaterm is configured you can save the configuration to the SD card, then
enter the terminal by selecting &lt;code&gt;Terminal mode&lt;&#x2F;code&gt; from the main menu.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;dial-up-over-voip-with-a-commodore-modem-from-1985&#x2F;novaterm4.jpg&quot; alt=&quot;Terminal mode of Novaterm program&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;This is the screen where data can be sent and received via the modem once it’s connected.
There’s a few other operations we can do from here. Press &lt;code&gt;C= M&lt;&#x2F;code&gt; for a list:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;dial-up-over-voip-with-a-commodore-modem-from-1985&#x2F;novaterm-help.jpg&quot; alt=&quot;List of commands for the Notaterm terminal&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Relevant for us are &lt;code&gt;C= H&lt;&#x2F;code&gt; and &lt;code&gt;C= J&lt;&#x2F;code&gt; which are described as “Hang up phone” and “Phone off hook” respectively.
Oddly I found that these commands did the opposite of what they say. When I press &lt;code&gt;C= H&lt;&#x2F;code&gt; I hear a dial tone
from the modem (from the speakers attached to its aftermarket headphone jack), and the attached analog phone powers off.
When the modem is in this state, playing the touch tones to dial a number into the “Audio In” port initiates a call.
Pressing &lt;code&gt;C= J&lt;&#x2F;code&gt; ends the current call and causes the analog phone to turn back on. Sometimes &lt;code&gt;C= J&lt;&#x2F;code&gt;
also causes a hissing static sound to start from the inside the modem. It’s not played through the speakers (nor the internal speaker which I disconnected).
The hissing can be stopped by picking up the handset of the analog phone.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;initiating-a-call&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#initiating-a-call&quot; aria-label=&quot;Anchor link for: initiating-a-call&quot;&gt;Initiating a Call&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;As described above there are two ways to start a call:&lt;&#x2F;p&gt;
&lt;h4 id=&quot;external-device&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#external-device&quot; aria-label=&quot;Anchor link for: external-device&quot;&gt;External Device&lt;&#x2F;a&gt;&lt;&#x2F;h4&gt;
&lt;p&gt;To initiate a call by playing touch tones into the “Audio In” jack, attach the device that will generate the tones with a 3.5mm to RCA adapter.
These adapters usually have two RCA plugs for the left and right audio channel, so just connect one of the plugs to the modem and let the other dangle.
Press &lt;code&gt;C= H&lt;&#x2F;code&gt; on the modem and you should hear a dial tone through the modem or its speakers.
Then play the touch tones for the number you want to dial. I found that 100ms tones with 50ms gaps works, but the timing may depend on your ATA.
You should hear the touch tones play out of the modem’s speakers, and as soon as the first touch tone plays the dial tone should stop.
If the dial tone persists throughout the touch tones and the call doesn’t connect, try increasing the volume of the device generating the touch tones.
A few seconds after the final touch tone played, you should hear a ring tone through the modem’s speakers.&lt;&#x2F;p&gt;
&lt;h4 id=&quot;analog-phone&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#analog-phone&quot; aria-label=&quot;Anchor link for: analog-phone&quot;&gt;Analog Phone&lt;&#x2F;a&gt;&lt;&#x2F;h4&gt;
&lt;p&gt;To initiate a call with the analog phone, press &lt;code&gt;C= J&lt;&#x2F;code&gt; to make sure the phone is powered on.
Pick up the phone, and you should hear a dial tone through the receiver (it’s just a regular phone at this point).
Dial the number you want to call, and wait until you hear a ring tone through the receiver.
Without hanging up the phone, press &lt;code&gt;C= H&lt;&#x2F;code&gt; on the Commodore. The analog phone will immediately go dead,
and you should now hear the ring tone play through the modem (or attached
speakers).&lt;&#x2F;p&gt;
&lt;h2 id=&quot;connecting&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#connecting&quot; aria-label=&quot;Anchor link for: connecting&quot;&gt;Connecting&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;We’re finally ready to go online.&lt;&#x2F;p&gt;
&lt;p&gt;Start a terminal (e.g. &lt;code&gt;picocom&lt;&#x2F;code&gt;) session on the PC, and make sure the modem is
alive by sending &lt;code&gt;AT&lt;&#x2F;code&gt; and getting a response &lt;code&gt;OK&lt;&#x2F;code&gt;, and make sure the “Terminal
Ready” light on the modem is on.&lt;&#x2F;p&gt;
&lt;p&gt;The Commodore and PC modems should be attached to the two phone ports of the ATA.
Note down which VoIP phone number is associated with each of the ports.
Then from the Commodore side, &lt;a href=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;dial-up-over-voip-with-a-commodore-modem-from-1985&#x2F;#initiating-a-call&quot;&gt;initiate a call&lt;&#x2F;a&gt;, dialing the number associated with the port which the PC’s modem is plugged into.
The terminal on the PC should periodically print &lt;code&gt;RING&lt;&#x2F;code&gt; indicating an incoming call.
Send the command &lt;code&gt;ATA&lt;&#x2F;code&gt; to the modem to answer the call.&lt;&#x2F;p&gt;
&lt;p&gt;Now the modems should handshake, indicated by some loud beeping from both. When
the beeping stops, the PC’s modem should print &lt;code&gt;CONNECT&lt;&#x2F;code&gt; to the terminal, and
on the top status line in Novaterm on the Commodore, the &lt;code&gt;C&lt;&#x2F;code&gt; should light up
and the timer should start incrementing.&lt;&#x2F;p&gt;
&lt;p&gt;Finally, on the PC side, adjust the baud rate to 300. Above I explained that both sides of a serial or modem connection must agree on the bit rate.
I initially assumed that the connection between both modems and the connection between the PC’s modem and the PC could operate at different rates,
as long as the digital side of the modem matched the PC’s serial port bit rate, and the analog side of the modem matched the other modem’s bit rate.
I expected the PC’s modem would buffer data from the PC at one rate, and transmit it at a different rate over the phone line.
Maybe other modems work this way, but mine does not, and I found that out the hard way. The Commodore modem always operates at 300 bits&#x2F;s, so the PC’s
serial port must be configured to the same speed. If you’re using &lt;code&gt;picocom&lt;&#x2F;code&gt;, you can change the baud rate with &lt;code&gt;C-a C-b&lt;&#x2F;code&gt; which will prompt for a new rate,
or you can start a &lt;code&gt;picocom&lt;&#x2F;code&gt; session with
&lt;code&gt;picocom -b 300 &#x2F;dev&#x2F;ttyS0&lt;&#x2F;code&gt;. The digital side of the modem will adjust its rate to match the rate of the PC’s serial port, which it can figure out dynamically because
it knows all commands will begin with a fixed sequence of bits making up the command prefix &lt;code&gt;AT&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;And now you’re connected! Typing into the &lt;code&gt;picocom&lt;&#x2F;code&gt; terminal will send characters to the Commodore and vice versa.&lt;&#x2F;p&gt;
&lt;p&gt;Here’s how the entire process looks on the Commodore side including the modem sounds. Note the spurious character received by the Commodore immediately after it connects.&lt;&#x2F;p&gt;
&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https:&#x2F;&#x2F;www.youtube.com&#x2F;embed&#x2F;nWYCyEs-7PY?si=jYRPzeUaX3v5Sp-f&quot; title=&quot;YouTube video player&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&quot; referrerpolicy=&quot;strict-origin-when-cross-origin&quot; allowfullscreen&gt;&lt;&#x2F;iframe&gt;
&lt;p&gt;The Linux PC side can interact with the remote side by redirecting IO to&#x2F;from &lt;code&gt;&#x2F;dev&#x2F;ttyS0&lt;&#x2F;code&gt;. Keep the serial terminal running
to maintain the connection, and in a separate terminal run something like &lt;code&gt;echo Hello, World! &amp;gt; &#x2F;dev&#x2F;ttyS0&lt;&#x2F;code&gt; to send the text to the remote machine.&lt;&#x2F;p&gt;
&lt;video autoplay muted loop&gt;
  &lt;source src=&quot;cowsay.mp4&quot; type=&quot;video&#x2F;mp4&quot;&gt;
  Your browser does not support the video element.
&lt;&#x2F;video&gt;
&lt;h2 id=&quot;everything-that-went-wrong&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#everything-that-went-wrong&quot; aria-label=&quot;Anchor link for: everything-that-went-wrong&quot;&gt;Everything that went wrong&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;Here’s a list of all the reasons this didn’t work the first time:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Plugging my ATA into my home network on the far side of a wireless bridge causes my internet to go down intermittently. The room where I keep my Commodore
is at the opposite end of my apartment to my router, and I don’t want to run an Ethernet cable to connect them. I was using an old router with WDS to connect
the Ethernet network in the room with the Commodore to the WIFI network from my main router. The ATA worked fine when plugged into my main router, but when
plugged into the far side of the WDS bridge it would cause my main router to lose connection with my ISP for a few minutes every hour or so. After days of
investigating I still don’t know why this happened, but I fixed the issue by installing a dedicated WIFI bridge instead of using WDS.&lt;&#x2F;li&gt;
&lt;li&gt;The Commodore modem can’t generate touch tones. It can still theoretically dial numbers using pulse dialing - the style of dialing used by rotary phones.
My ATA can be configured to allow pulse dialing, but I still couldn’t make it work. I spent a while trying to find a Commodore terminal which also supported
generating touch tones on the sound chip, and connecting the Commodore’s audio out to the modem’s audio in, but it still wouldn’t initiate a call, possibly because
of the quality or volume of the touch tones. Only then did I experiment with using an external device to generate the tones, which worked.&lt;&#x2F;li&gt;
&lt;li&gt;The NetComm modem only handshakes when its baud rate is set to “auto”. The Hayes AT commands let you change the baud rate of the modem, and I thought
I could rule out an incorrect baud rate as the problem by forcing it to use 300 baud. Running &lt;code&gt;ATS37=1&lt;&#x2F;code&gt; forces 300 baud, but the consequence was it would no longer
handshake with the Commodore modem, despite the latter running at 300 baud. Changing the NetComm modem’s baud to auto mode (&lt;code&gt;ATS37=0&lt;&#x2F;code&gt;) allowed the
handshake to complete. Auto mode is the default setting, and I observed it could handshake before I changed it, but not reliably (due to other issues)
so I changed it. Only upon changing it back was the modem able to handshake.&lt;&#x2F;li&gt;
&lt;li&gt;I bought the wrong 2 port ATA. I was originally using a 1 port ATA for the Commodore side, and my router’s built-in ATA for the PC side. It wasn’t working
due to all the other reasons on this list, so I decided to buy a 2 port ATA of the same model as a video I found with a similar setup to mine to rule out
the ATA as the problem. I bought a second-hand Cisco ATA 191 without realizing that there are two models of the ATA 191 - a multiplatform model
which allows configuration through a web UI - and an enterprise model which is locked down. And I bought the wrong one.&lt;&#x2F;li&gt;
&lt;li&gt;My serial cable was apparently faulty, as the “Terminal Ready” light on the modem didn’t turn on at first. The symptom was that I could send commands
to the modem, and even have it handshake, but the PC couldn’t send or receive any data via the modem. Fortunately I had a spare which worked.&lt;&#x2F;li&gt;
&lt;li&gt;I didn’t realize the PC’s serial port needs to match the baud rate of the connection between the two modems. I assumed I could use a faster baud rate
when sending data from the PC to the modem, and the modem would buffer the data and send data to the other modem at a slower speed. Surely some
modems must work like this, otherwise the servers providing dial-up services would need to constantly adjust their baud rates to match the caller’s
modems, and the Hayes AT commands don’t provide a way to find out the current baud rate of the analog side of the modem.&lt;&#x2F;li&gt;
&lt;li&gt;On the Commodore side, I missed that you had to set the serial driver to “User port”. I’d already set the modem type to 1660, which refers to this
specific model of modem which can only be connected via the user port so I didn’t look for another setting about the interface between the computer and modem
at first.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Only after everything on that list was addressed was any data able to make it across the dial-up connection, however
everything I tried to send would be received garbled. Bytes received by the PC from the commodore were mostly made up of 1 bits, like &lt;code&gt;OxFF&lt;&#x2F;code&gt; and &lt;code&gt;OxFE&lt;&#x2F;code&gt;.
Data sent from the PC to the Commodore would be “spread out”, with each byte sent being received as 4 or 5 bytes. But at least there was a consistent mapping
from character sent to characters received. The problem turned out to be that I had disabled the jitter buffer on the ATA, following the principle of
“disable all the fancy ATA voice features”. It turns out this feature is important for preventing interruptions to the call caused by routing the call
over the internet. It adds latency but otherwise doesn’t affect the signal. Once I enabled the jitter buffer I could send and receive data over the dial-up network.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;next-steps&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#next-steps&quot; aria-label=&quot;Anchor link for: next-steps&quot;&gt;Next Steps&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;My eventual goal is to host a dial-up bulletin board system. I think this will require a different modem, as currently I need to change the PC’s
baud rate to match the dial-up connection in order to send data over it, and there’s no way to determine this automatically.&lt;&#x2F;p&gt;
&lt;p&gt;Looking around online
there doesn’t seem to be any maintained dial-up BBS software for Linux. I think my best bet will be to emulate an
old computer on the Linux machine and connect the serial port to the emulator, then find some BBS software for the emulated machine.&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Restoring my Childhood Family Computer Part 4: Emulation</title>
          <pubDate>Tue, 30 Dec 2025 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/restoring-my-childhood-family-computer-part-4/</link>
          <guid>https://www.gridbugs.org/restoring-my-childhood-family-computer-part-4/</guid>
          <description xml:base="https://www.gridbugs.org/restoring-my-childhood-family-computer-part-4/">&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;restoring-my-childhood-family-computer-part-4&#x2F;banner.jpg&quot; alt=&quot;Close-up of the logo on the front of my Macintosh LC575&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Wow has it really been over a year since I posted an update on this project?
In &lt;a href=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;restoring-my-childhood-family-computer-part-3&#x2F;&quot;&gt;Part 3&lt;&#x2F;a&gt; I
powered on the machine with the new motherboard. It played the Macintosh startup chime
followed by a &lt;a href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;Macintosh_startup#Chimes_of_Death&quot;&gt;Chime of Death&lt;&#x2F;a&gt;
and did not boot.&lt;&#x2F;p&gt;
&lt;p&gt;Before continuing with repairing the hardware I wanted to try taking an image of the hard drive
in case I made a mistake and damage it.&lt;&#x2F;p&gt;
&lt;p&gt;Here’s the drive:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;restoring-my-childhood-family-computer-part-4&#x2F;drive.jpg&quot; alt=&quot;The SCSI hard drive from my Macintosh&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;The drive has a SCSI port, so to attach it to a modern computer I needed an
adapter. The only adapter I could find on ebay was this PCI card:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;restoring-my-childhood-family-computer-part-4&#x2F;adapter.jpg&quot; alt=&quot;SCSI interface on a PCI card&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;PCI is also quite an old connector and none of my computers had a PCI
slot. I went and bought and old-ish second-hand PC on ebay with a PCI
slot.&lt;&#x2F;p&gt;
&lt;p&gt;Then I chickened out and contacted a local data recovery company. I brought them
the drive and waited.&lt;&#x2F;p&gt;
&lt;p&gt;After not hearing back for a few weeks I called them and it turned out they
didn’t have the right adapter, so I lent them mine and waited again.&lt;&#x2F;p&gt;
&lt;p&gt;After about a month of delays I gave up waiting, took back my drive and adapter,
and attempted the recovery myself.&lt;&#x2F;p&gt;
&lt;p&gt;I verified that Linux could recognize the SCSI adapter in my second-hand
computer. I powered down the machine, attached the SCSI ribbon cable and the
&lt;a href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;Molex_connector&quot;&gt;Molex&lt;&#x2F;a&gt; power cable.&lt;&#x2F;p&gt;
&lt;p&gt;When I turned the machine back on the drive lit up with sparks and released some
purple smoke.&lt;&#x2F;p&gt;
&lt;p&gt;It left this faint shadow on my desk:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;restoring-my-childhood-family-computer-part-4&#x2F;burn-mark.jpg&quot; alt=&quot;Desk surface with a faint outline of a drive in soot&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;I still don’t know what went wrong. I have a spare SCSI hard drive but I’m not
about to waste it by plugging it into a faulty adapter and destroying it too.
My best theory is that the drive was already damaged. The Macintosh had
been left outside in a tropical location for several years. It’s possible that
the drive’s PCB had rusted and when power was applied it shorted out or a
damaged part overheated. If this is the case then it’s odd that there were no
fireworks when I tried powering on the Macintosh with the hard drive still
attached, but maybe its power supply wasn’t powerful enough to do this sort of
damage? Perhaps that explains the strange noises I mentioned in the previous
post.&lt;&#x2F;p&gt;
&lt;p&gt;Once the smoke cleared it occurred to me that
while the drive’s PCB was fried, it’s possible that the disk inside was
undamaged. I contacted a second, better data recovery company, told them the story, and
brought them the drive. They had a bunch of working SCSI drive PCBs
and they could swap the dead one for a live one and attempt to recover
the data.&lt;&#x2F;p&gt;
&lt;p&gt;I committed to paying something like AUD $1000 &lt;em&gt;regardless of the outcome&lt;&#x2F;em&gt; (I
really wanted this data!) and nervously waited for several weeks.&lt;&#x2F;p&gt;
&lt;p&gt;In the end they were able to recover my data. Normally they make you pay for an
external hard drive with the recovered files on it but this is a 250mb hard
drive from 1994 so they just sent me a link to a google drive.&lt;&#x2F;p&gt;
&lt;p&gt;I loaded the disk image into the &lt;a href=&quot;https:&#x2F;&#x2F;basilisk.cebix.net&quot;&gt;Basilisk II&lt;&#x2F;a&gt;
Macintosh emulator and:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;restoring-my-childhood-family-computer-part-4&#x2F;boot-error.png&quot; alt=&quot;MacOS System 7 boot error with a “Restart” button&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Honestly that is a better outcome than I was expecting. It’s kinda surreal that
a disk image from a real hard drive can just boot up in an emulator like this.&lt;&#x2F;p&gt;
&lt;p&gt;Clicking the “Restart” button in the error window just causes the window to
pop up again, so it seemed like the machine was stuck in a loop, but for some
reason I insisted on pressing “Restart” probably 100 times (sometimes with the shift key down,
sometimes without, not sure if it matters), and &lt;em&gt;eventually&lt;&#x2F;em&gt; the machine booted
successfully:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;restoring-my-childhood-family-computer-part-4&#x2F;boot-success.png&quot; alt=&quot;MacOS System 7 desktop&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;And wow! I’m still amazed that this worked, and the data had just been sitting
on the disk for years. I’ve always associated this computer with my dad because
he was the main person who used it. He died when I was a kid and it was an
emotional moment seeing this machine running again, even if it’s just running virtually (for now!).&lt;&#x2F;p&gt;
&lt;p&gt;The “steve” file on the desktop is a Kings Quest 7 save
file but I’m not sure if it’s mine or my dad’s because his name was also Steve.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;restoring-my-childhood-family-computer-part-4&#x2F;macintosh-hd.png&quot; alt=&quot;MacOS System 7 “Macintosh HD” folder&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;I remember the day when I learnt that you could colour-code icons and spent
hours traversing the filesystem, colouring everything.&lt;&#x2F;p&gt;
&lt;p&gt;Also what’s that “Bad Boy” file? It gave an error when opening and I didn’t
think much of it until I sat down to write this post, but more on that below.&lt;&#x2F;p&gt;
&lt;p&gt;Here’s our collection of games. Most of these are from a demo disk. More colour-coding evident here.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;restoring-my-childhood-family-computer-part-4&#x2F;games.png&quot; alt=&quot;Games folder&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Stephen’s various balloons in the above screenshot are save files from the
Fokker Triplane Flight Simulator, the game on the bottom-left of the box
pictured below:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;restoring-my-childhood-family-computer-part-4&#x2F;simulator-pack.jpg&quot; alt=&quot;Mac Simulator Pack game box&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Speaking of balloons, the question mark in a speech bubble at the top-right of
the screen enters “Balloon Help” mode where mousing over any entity displays an
explanation of that entity.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;restoring-my-childhood-family-computer-part-4&#x2F;help2.png&quot; alt=&quot;Help bubble explaining the File menu&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;I’m tickled because it explains concepts that feel so obvious, but this was a
time when the visual language of a windowed operating system shell was
unfamiliar to most people.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;restoring-my-childhood-family-computer-part-4&#x2F;help1.png&quot; alt=&quot;Help bubble explaining the Title bar&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;The help bubble is such a neat idea and it’s a shame it’s not common on
modern OSes. Searching a manual or the web requires naming the thing you want
help about, and how are users supposed to know it’s called a “Title bar”?&lt;&#x2F;p&gt;
&lt;p&gt;My dad used this computer for his work as a school teacher, and I was in his
grade 2 class. The disk had a bunch of my old creative writing assignments
on it. It also has assignments from all my classmates, which I won’t share here,
but if you were in Mr Sherratt’s grade 2 class in 1999 at Cardwell State School,
and want some of your old writing, get in touch (also hello, it’s been a
while!).&lt;&#x2F;p&gt;
&lt;p&gt;Here’s the first few pages of a story I found on the disk, written by me:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;restoring-my-childhood-family-computer-part-4&#x2F;lost-in-space0.png&quot; alt=&quot;The cover of a short story titled “Lost in Space”&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;The font is called “Beginners Alphabet” and it’s always italicized. I
remember initially being taught to write with a slant so maybe it’s a good font
for children still learning to write. I’ve heard that the state government
prescribes fonts for use in schools.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;restoring-my-childhood-family-computer-part-4&#x2F;lost-in-space1.png&quot; alt=&quot;The first page of the short story “Lost in Space”&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;I have a lot of nostalgia for this font. I’d completely forgotten about it but
it was everywhere at my school.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;restoring-my-childhood-family-computer-part-4&#x2F;desk-alphabet.png&quot; alt=&quot;An alphabet of upper and lower case letters as commonly seen in classrooms&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;My dad must have printed out these labels to put around the classroom:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;restoring-my-childhood-family-computer-part-4&#x2F;labels1.png&quot; alt=&quot;Some labels for objects in a classroom&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;We had Kid Pix!&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;restoring-my-childhood-family-computer-part-4&#x2F;kid-pix.png&quot; alt=&quot;The menu screen of Kid Pix&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;I remember designing this flying car in Kid Pix that would burn rubbish as fuel. Kinda
reminds of something from a movie I saw once…&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;restoring-my-childhood-family-computer-part-4&#x2F;flying-car.png&quot; alt=&quot;Kid Pix screenshot with an image of a technical drawing of a flying car obviously made by a child (me!)&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Speckled all over the filesystem were documents like this one. Oddly I don’t
remember actually owning a copy of Lemmings as a kid despite being obsessed with
playing it on the computers at school (though curiously the full version of
Lemmings is in the games folder). Lemmings doesn’t have save files; it just
prints a unique code for each level to type in the next time you play. I wrote a
bunch of these down on paper and must have typed them up at one point.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;restoring-my-childhood-family-computer-part-4&#x2F;lemmings-codes.png&quot; alt=&quot;Document with a list of Lemmings level codes&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;I didn’t actually make it to the end of Lemmings until a few years ago.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;restoring-my-childhood-family-computer-part-4&#x2F;lemmings.png&quot; alt=&quot;Lemmings gameplay&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Here’s the “steve” file from the desktop loaded in Kings Quest 7. I (or
perhaps my dad?) was most of the way through (we completed it many times
though).&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;restoring-my-childhood-family-computer-part-4&#x2F;kq7-bad-colour.png&quot; alt=&quot;King’s Quest 7 with incorrect colours&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;The colours usually didn’t mess up like this on the real hardware.
Oddly enough, restarting the emulator with a higher resolution fixes the colours,
but now you have to play the game in a window.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;restoring-my-childhood-family-computer-part-4&#x2F;kq7.png&quot; alt=&quot;King’s Quest 7 with correct colours running in a window&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;To avoid the error loop on startup and to fix some other issues such as the sound not working, I made a fresh install of MacOS System 7 on a new virtual
drive and mounted the original disk image as a secondary drive. The “disk1” in the
above screenshot is the new boot disk. The “Unix” in the above screenshot
corresponds to a directory on the host machine and makes it possible to transfer
files between the virtual Macintosh and the real computer the emulator is
running on.&lt;&#x2F;p&gt;
&lt;p&gt;Ok so what was up with that “Bad Boy” file from earlier? It’s the file on the
left on the window below about halfway down.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;restoring-my-childhood-family-computer-part-4&#x2F;macintosh-hd.png&quot; alt=&quot;MacOS System 7 “Macintosh HD” folder&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Opening the file tries to launch HyperStudio but then it gives an error that the
file is not a stack:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;restoring-my-childhood-family-computer-part-4&#x2F;bad-boy-error1.png&quot; alt=&quot;Error message that the file can’t be loaded because it’s not a stack&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;And then it takes you to this screen, asking to insert the “Kingfisher 101
Activities CD”.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;restoring-my-childhood-family-computer-part-4&#x2F;bad-boy-error2.png&quot; alt=&quot;Message asking to insert a CD&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Better do what it says.&lt;&#x2F;p&gt;
&lt;p&gt;The Kingfisher CD contains this program with several mini games.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;restoring-my-childhood-family-computer-part-4&#x2F;kingfisher1.png&quot; alt=&quot;A grid of mini games&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;The code game immediately caught my eye, and not for the first time.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;restoring-my-childhood-family-computer-part-4&#x2F;kingfisher2.png&quot; alt=&quot;A load game screen for a code mini game&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Again this could have been my dad and not me. I have no memory of this game
but a secret code from either my dad or myself is a pretty neat find.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;restoring-my-childhood-family-computer-part-4&#x2F;kingfisher3.png&quot; alt=&quot;The code minigame with a secret message and a prompt for a 4 digit code&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Now what was that code again?&lt;&#x2F;p&gt;
&lt;p&gt;I tried encrypting the alphabet to see if there’s an obvious pattern.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;restoring-my-childhood-family-computer-part-4&#x2F;kingfisher4.png&quot; alt=&quot;The code minigame, but this time it displays and upper and lower case alphabet&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Every time you press the “Lock&#x2F;Unlock” button the dog barks.&lt;&#x2F;p&gt;
&lt;p&gt;The 4 digit code turns out not to affect the result so the game must be just
storing it somewhere and applying the same transformation regardless of the
code.&lt;&#x2F;p&gt;
&lt;p&gt;The alphabets above map to the following:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;restoring-my-childhood-family-computer-part-4&#x2F;kingfisher5.png&quot; alt=&quot;The code minigame with the encoded alphabet: MLKJIHGFEDCBA@?&amp;gt;=&amp;lt;;;987654 mlkjihgfedcba`_^]\[ZYXWVUT&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;There’s a pretty clear pattern. I started to suspect that re-encoding the secret
message would also decode it so I typed out the encoded message and then locked
it with an arbitrary key:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;restoring-my-childhood-family-computer-part-4&#x2F;kingfisher6.png&quot; alt=&quot;The code minigame with the message: Hello everybody. You have guessed my secret code!&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;What was I doing again?&lt;&#x2F;p&gt;
&lt;p&gt;Inspecting the raw contents of the “Bad Boy” file in a hex editor
didn’t reveal anything useful. I thought maybe opening it in a text
editor on the Macintosh might show some useful strings in whatever text encoding it
uses, but when I tried it displayed this:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;restoring-my-childhood-family-computer-part-4&#x2F;bad-boy.png&quot; alt=&quot;MacOS System 7 desktop with a cartoon image of the face of a boy with green skin and his tongue poking out.&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;I don’t understand how file types work on this machine. The OS knows (or at
least thinks it knows) what
program to launch when you double click a file but the files don’t have
extensions in their name. Somehow Finder believes this file file is a HyperStudio stack, and when
you try to open it in a text editor (TeachText) it correctly recognizes it as an
image and displays it in a window, but the current program is still TeachText.&lt;&#x2F;p&gt;
&lt;p&gt;TeachText appears to have been made by Apple so let’s see if the help bubble
has anything to say about it.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;restoring-my-childhood-family-computer-part-4&#x2F;help3.png&quot; alt=&quot;The help bubble for TeachText explains that it can be used to display certain graphics&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;I freaking love help balloons.&lt;&#x2F;p&gt;
&lt;p&gt;So TeachText does know how display certain types of graphics!&lt;&#x2F;p&gt;
&lt;p&gt;But what does this have to do with HyperStudio or the Kingfisher 101 disk?&lt;&#x2F;p&gt;
&lt;p&gt;Well Kingfisher 101 is implemented as a HyperStudio stack. And take a look at the
“photofit” game. It’s icon looks a bit like our bad boy.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;restoring-my-childhood-family-computer-part-4&#x2F;kingfisher1.png&quot; alt=&quot;The Kingfisher 101 main menu. The photofit game has an icon that resembles the bad boy image&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;And running that game it’s clear how the bad boy image was created:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;restoring-my-childhood-family-computer-part-4&#x2F;kingfisher7.png&quot; alt=&quot;The photofit game displays a face very similar to the bad boy image&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;So maybe the OS is keeping track of which program &lt;em&gt;created&lt;&#x2F;em&gt; each file, and it
runs that program when you double click the file? And in this case the file was
created by Kingfisher 101 (presumably by clicking the “save” button in the
screenshot above), but the file is just a regular image. So the OS recorded that
the image file was created by Kingfisher (or maybe HyperStudio?) and it’s
trying to open the file with that program rather than a program that knows how
to display images like TeachText.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;restoring-my-childhood-family-computer-part-4&#x2F;bad-boy-info.png&quot; alt=&quot;The file info screen for the bad boy image where its kind is a HyperStudio document&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Confusingly there doesn’t seem to be a way to change the kind of a file, so I
don’t know how to make it so that double-clicking on it will have it open in the
correct program.&lt;&#x2F;p&gt;
&lt;p&gt;I’ve spent many hours exploring this disk. It’s a fascinating look into the
history of computing as well as my own past. I found a large collection of my
dad’s old work and personal files which I’ve converted to google docs and shared
them with family members, and also put together a package with all the files
necessary to run our old Mac emulated on Windows, along with disk images for all
the games we used to play.&lt;&#x2F;p&gt;
&lt;p&gt;I feel like I was born at the perfect time for experiencing this sort of
nostalgia. In the 90s computers were powerful enough to support a wide range of
software accessible to most people, ubiquitous enough that you probably had one
in your home, but scarce enough that the family had to share. Users had to
figure out how to organize their files and software. Through their shared use machines
developed a kind of personality.&lt;&#x2F;p&gt;
&lt;p&gt;Computers were simple
enough to be easily emulated on modern hardware, and lacked security
features that might get in the way. And it’s worth going through the trouble to
experience that personality again.&lt;&#x2F;p&gt;
&lt;p&gt;Most desktop apps have been replaced with web apps
and physical media with app stores.
Nobody is forced to share a computer.
OSes abstract more of the details that most people find irrelevant.
Devices are locked down far beyond the point of protecting users.
Since the 90s computers have arguably improved at letting you do the things you want
to get done but I can’t shake the feeling that we’ve optimized away something
magical.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;restoring-my-childhood-family-computer-part-4&#x2F;mouse-practice-wave.png&quot; alt=&quot;Mouse practice avatar waving&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;That’s all for now. The next step restoring the Macintosh will be trying to get
it working with my spare SCSI drive. I got my hands on some MacOS System 7 install
disks so barring additional hardware problems I’ll be able to install a fresh copy of MacOS.&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Porting my Toy OCaml Build System to Windows</title>
          <pubDate>Thu, 28 Aug 2025 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/porting-my-toy-ocaml-build-system-to-windows/</link>
          <guid>https://www.gridbugs.org/porting-my-toy-ocaml-build-system-to-windows/</guid>
          <description xml:base="https://www.gridbugs.org/porting-my-toy-ocaml-build-system-to-windows/">&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;porting-my-toy-ocaml-build-system-to-windows&#x2F;banner.jpg&quot; alt=&quot;A grassy hill with a blue sky containing a single cloud. The closest thing to the Windows XP Bliss wallpaper within walking distance of my home.&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;alicecaml&#x2F;alice&quot;&gt;Alice&lt;&#x2F;a&gt; is my toy OCaml build system
project where I ask “What if &lt;a href=&quot;https:&#x2F;&#x2F;doc.rust-lang.org&#x2F;stable&#x2F;cargo&#x2F;&quot;&gt;Cargo&lt;&#x2F;a&gt;
but for OCaml?”. My main priority when designing Alice is accessibility but
perhaps a more suitable term would be &lt;em&gt;inclusivity&lt;&#x2F;em&gt; as my goal is for the tool
to be usable by as many people as possible. Alice is still in its infancy and is
currently used by &lt;em&gt;nobody&lt;&#x2F;em&gt; but if the day comes when it becomes a viable tool
for building OCaml software I would hate to systematically exclude a potential
user base because of baked-in assumptions made early in its design.&lt;&#x2F;p&gt;
&lt;p&gt;I do most of my development on Linux and macOS which means I’m likely to
make design decisions favouring those systems, possibly at the expense of
potential users on other systems. In particular, because Windows differs so much
from other popular OSes due to it not being Unix-based, there’s a significant
risk of excluding Windows users if I don’t make a conscious effort to support
them.&lt;&#x2F;p&gt;
&lt;p&gt;Like most people who grew up in the 2000s or later I was introduced to
computing on home and school computers running Windows (Windows 98 in my case!).
I started playing with Linux in 2009 and it gradually became my daily driver
but it took a huge amount of free time messing around with different distros and
learning the tools and conventions to get to my level of comfort. I’m also
fortunate enough to have the means to afford a Mac. But people who learnt
Windows first and lack the time, money, or inclination to switch to a
Unix-based OS will remain Windows users. So when Windows users are excluded from
a tool, who is &lt;em&gt;really&lt;&#x2F;em&gt; being excluded?&lt;&#x2F;p&gt;
&lt;p&gt;Today I’m going to port Alice to Windows.&lt;&#x2F;p&gt;
&lt;p&gt;To prepare for this work I’ve compiled a relocatable OCaml compiler toolchain for
Windows based on &lt;a href=&quot;https:&#x2F;&#x2F;www.dra27.uk&#x2F;blog&#x2F;&quot;&gt;David Allsopp&lt;&#x2F;a&gt;’s patches to allow
the compiler to be moved after its initial installation. See &lt;a href=&quot;https:&#x2F;&#x2F;www.youtube.com&#x2F;watch?v=5JDSUCx-tPw&quot;&gt;this talk from
ICFP 2022&lt;&#x2F;a&gt; for more info. Without
this work every user would need to compile the OCaml compiler on their machine before
using it. It’s important to me that a user of Alice can get started writing
OCaml as quickly as possible with no hurdles. Building the compiler from source can
take over 10 minutes and I don’t want users’ first experience of Alice to be
waiting such a long time. I don’t think I would have started working on Alice
at all if distributing a pre-compiled relocatable compiler wasn’t an option.&lt;&#x2F;p&gt;
&lt;p&gt;Technically I could have used opam to bootstrap my development environment as it works
&lt;a href=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;sound-on-ocaml-on-windows&#x2F;#trying-again-with-msys2&quot;&gt;perfectly fine&lt;&#x2F;a&gt;
on Windows but one cool feature of Alice is that it can download pre-compiled
development tools for you. Alice can’t build itself (yet!) but I still want to
eat my own dogfood when I can, and so I’m testing out the prebuilt toolchain
while developing Alice. I haven’t updated Alice to be able to install the
dev tools prebuilt for Windows yet, but I have a handy
&lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;alicecaml&#x2F;alice&#x2F;blob&#x2F;main&#x2F;boot&#x2F;x86_64-windows.sh&quot;&gt;shell script&lt;&#x2F;a&gt;
that sets up a development environment for working on Alice using the same tools
as Alice would install if it &lt;em&gt;was&lt;&#x2F;em&gt; already built. Classic bootstrapping problem.&lt;&#x2F;p&gt;
&lt;p&gt;I’m using powershell and I have &lt;a href=&quot;https:&#x2F;&#x2F;www.msys2.org&#x2F;&quot;&gt;msys2&lt;&#x2F;a&gt; installed so
some commands will look very Unix-y. Alice itself will work fine on Windows once
ported but I barely know what I’m doing in powershell so I’ll stick to what I
know (ie. Unix commands from msys2) while setting up my environment!&lt;&#x2F;p&gt;
&lt;p&gt;This command installed a prebuilt compiler toolchain to &lt;code&gt;D:\alice\current&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;PS D:\src\alice&amp;gt; sh boot\x86_64-windows.sh D:\alice
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The result:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;PS D:\src\alice&amp;gt; ls D:\alice\current\bin\
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    Directory: D:\alice\current\bin
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;Mode                 LastWriteTime         Length Name
&lt;&#x2F;span&gt;&lt;span&gt;----                 -------------         ------ ----
&lt;&#x2F;span&gt;&lt;span&gt;-a----        2025-08-28     02:07         451937 flexlink.byte.exe
&lt;&#x2F;span&gt;&lt;span&gt;-a----        2025-08-28     02:08        4706378 flexlink.exe
&lt;&#x2F;span&gt;&lt;span&gt;-a----        2025-08-28     02:08        4706378 flexlink.opt.exe
&lt;&#x2F;span&gt;&lt;span&gt;-a----        2025-08-28     02:08       24365426 ocaml.exe
&lt;&#x2F;span&gt;&lt;span&gt;-a----        2025-08-28     02:08        3394118 ocamlc.byte.exe
&lt;&#x2F;span&gt;&lt;span&gt;-a----        2025-08-28     02:08       13950043 ocamlc.exe
&lt;&#x2F;span&gt;&lt;span&gt;-a----        2025-08-28     02:08       13950043 ocamlc.opt.exe
&lt;&#x2F;span&gt;&lt;span&gt;...
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;In addition to the compiler, this command installed &lt;code&gt;ocamlformat&lt;&#x2F;code&gt; and &lt;code&gt;ocamllsp&lt;&#x2F;code&gt;,
which I also pre-compiled for Windows in preparation for this work.&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;PS D:\src\alice&amp;gt; Get-Command ocamlformat
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;CommandType     Name               Version    Source
&lt;&#x2F;span&gt;&lt;span&gt;-----------     ----               -------    ------
&lt;&#x2F;span&gt;&lt;span&gt;Application     ocamlformat.exe    0.0.0.0    D:\alice\current\bin\ocamlformat.exe
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;PS D:\src\alice&amp;gt; Get-Command ocamllsp
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;CommandType     Name               Version    Source
&lt;&#x2F;span&gt;&lt;span&gt;-----------     ----               -------    ------
&lt;&#x2F;span&gt;&lt;span&gt;Application     ocamllsp.exe       0.0.0.0    D:\alice\current\bin\ocamllsp.exe
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The other development tool I’ll need is &lt;code&gt;dune&lt;&#x2F;code&gt;. Alice uses Dune’s relatively new
package-management features and these have not yet been ported to Windows so
I’ve needed to hack Dune a little bit to make it work here. I already did this
work while building &lt;code&gt;ocamlformat&lt;&#x2F;code&gt; and &lt;code&gt;ocamllsp&lt;&#x2F;code&gt; with Dune package management on
Windows. Most hacks were to those projects’ lockfiles rather than to Dune itself.
I kept a
&lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;alicecaml&#x2F;alice&#x2F;blob&#x2F;1c934ce6eb096130659268e913e4da54b7b2853c&#x2F;tool-build-scripts&#x2F;5.3.1&#x2F;windows-notes.md&quot;&gt;log&lt;&#x2F;a&gt;
of everything I needed to change. I may be referring to it in order to get Alice
to build with Dune on Windows.&lt;&#x2F;p&gt;
&lt;p&gt;I built Dune from source on Windows using opam while building &lt;code&gt;ocamlformat&lt;&#x2F;code&gt; and
&lt;code&gt;ocamllsp&lt;&#x2F;code&gt; and that’s the Dune executable I’ll be using for today’s work on
Alice.&lt;&#x2F;p&gt;
&lt;p&gt;Ok so I have all the tools I need, let’s see if Alice builds on Windows:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;PS D:\src\alice&amp;gt; dune build
&lt;&#x2F;span&gt;&lt;span&gt;File &amp;quot;dune.lock&#x2F;lock.dune&amp;quot;, lines 18-29, characters 1-288:
&lt;&#x2F;span&gt;&lt;span&gt;18 |  ((arch x86_64)
&lt;&#x2F;span&gt;&lt;span&gt;19 |   (os linux)
&lt;&#x2F;span&gt;&lt;span&gt;20 |   (sys-ocaml-version 5.3.1+relocatable))
&lt;&#x2F;span&gt;&lt;span&gt;....
&lt;&#x2F;span&gt;&lt;span&gt;27 |  ((arch arm64)
&lt;&#x2F;span&gt;&lt;span&gt;28 |   (os macos)
&lt;&#x2F;span&gt;&lt;span&gt;29 |   (sys-ocaml-version 5.3.1+relocatable)))
&lt;&#x2F;span&gt;&lt;span&gt;Error: The lockdir does not contain a solution compatible with the current
&lt;&#x2F;span&gt;&lt;span&gt;platfort.
&lt;&#x2F;span&gt;&lt;span&gt;The current platform is:
&lt;&#x2F;span&gt;&lt;span&gt;- arch = x86_64
&lt;&#x2F;span&gt;&lt;span&gt;- os = win32
&lt;&#x2F;span&gt;&lt;span&gt;- os-distribution = win32
&lt;&#x2F;span&gt;&lt;span&gt;- os-family = windows
&lt;&#x2F;span&gt;&lt;span&gt;- os-version = 10.0.22621
&lt;&#x2F;span&gt;&lt;span&gt;- sys-ocaml-version = 5.3.1+relocatable
&lt;&#x2F;span&gt;&lt;span&gt;Hint: Try adding the following to dune-workspace:
&lt;&#x2F;span&gt;&lt;span&gt;Hint: (lock_dir (solve_for_platforms ((arch x86_64) (os win32))))
&lt;&#x2F;span&gt;&lt;span&gt;Hint: ...and then rerun &amp;#39;dune pkg lock&amp;#39;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;I’ve seen this error before while building &lt;code&gt;ocamlformat&lt;&#x2F;code&gt;. Alice has &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;alicecaml&#x2F;alice-opam-repo&quot;&gt;its own
little opam repository&lt;&#x2F;a&gt; which
allows it to use the pre-compiled relocatable OCaml toolchain. The relocatable
OCaml toolchain has a version which is not released on the main opam repository
which means the &lt;code&gt;ocaml-system&lt;&#x2F;code&gt; package can’t be used, so I needed to make a new
version of &lt;code&gt;ocaml-system&lt;&#x2F;code&gt; matching the version of the pre-compiled toolchain
(&lt;code&gt;5.3.1+relocatable&lt;&#x2F;code&gt;). I did this months ago when first getting Alice working
on Linux and macOS but the version of the &lt;code&gt;ocaml-system&lt;&#x2F;code&gt; package originally had
some logic preventing its installation on Windows (copied from the upstream
&lt;code&gt;ocaml-system&lt;&#x2F;code&gt; package), but that logic turned out to be unnecessary for my use
case so I just
&lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;alicecaml&#x2F;alice-opam-repo&#x2F;commit&#x2F;781db10863f3b7a3507842e88d0d3beeebd264ad&quot;&gt;deleted it&lt;&#x2F;a&gt;.
However I did so recently and that change hasn’t made its way into Alice yet.
Making that change to Alice was
straightforward:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;patch&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-patch &quot;&gt;&lt;code class=&quot;language-patch&quot; data-lang=&quot;patch&quot;&gt;&lt;span&gt;diff --git a&#x2F;dune-workspace b&#x2F;dune-workspace
&lt;&#x2F;span&gt;&lt;span&gt;index bc77561..8d8b9db 100644
&lt;&#x2F;span&gt;&lt;span style=&quot;background-color:#ffecec;font-weight:bold;font-style:italic;color:#bd2c00;&quot;&gt;---&lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt; a&#x2F;dune-workspace
&lt;&#x2F;span&gt;&lt;span style=&quot;background-color:#eaffea;font-weight:bold;font-style:italic;color:#55a532;&quot;&gt;+++&lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt; b&#x2F;dune-workspace
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;font-style:italic;color:#969896;&quot;&gt;@@ -7,7 +7,7 @@
&lt;&#x2F;span&gt;&lt;span&gt; (repository
&lt;&#x2F;span&gt;&lt;span&gt;  (name alice_frozen)
&lt;&#x2F;span&gt;&lt;span&gt;  (url
&lt;&#x2F;span&gt;&lt;span style=&quot;background-color:#ffecec;color:#323232;&quot;&gt;-  git+https:&#x2F;&#x2F;github.com&#x2F;alicecaml&#x2F;alice-opam-repo#9957c6334ca7ea18a973ea2fa9e3e56ab9c85eeb))
&lt;&#x2F;span&gt;&lt;span style=&quot;background-color:#eaffea;font-weight:bold;color:#55a532;&quot;&gt;+&lt;&#x2F;span&gt;&lt;span style=&quot;background-color:#eaffea;color:#323232;&quot;&gt;  git+https:&#x2F;&#x2F;github.com&#x2F;alicecaml&#x2F;alice-opam-repo#781db10863f3b7a3507842e88d0d3beeebd264ad))
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt; (repository
&lt;&#x2F;span&gt;&lt;span&gt;  (name upstream_frozen)
&lt;&#x2F;span&gt;&lt;span&gt;diff --git a&#x2F;dune.lock&#x2F;lock.dune b&#x2F;dune.lock&#x2F;lock.dune
&lt;&#x2F;span&gt;&lt;span&gt;index 5ef36e2..fdc34a5 100644
&lt;&#x2F;span&gt;&lt;span style=&quot;background-color:#ffecec;font-weight:bold;font-style:italic;color:#bd2c00;&quot;&gt;---&lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt; a&#x2F;dune.lock&#x2F;lock.dune
&lt;&#x2F;span&gt;&lt;span style=&quot;background-color:#eaffea;font-weight:bold;font-style:italic;color:#55a532;&quot;&gt;+++&lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt; b&#x2F;dune.lock&#x2F;lock.dune
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;font-style:italic;color:#969896;&quot;&gt;@@ -12,7 +12,7 @@
&lt;&#x2F;span&gt;&lt;span&gt;   ((source
&lt;&#x2F;span&gt;&lt;span&gt;     https:&#x2F;&#x2F;github.com&#x2F;ocaml-dune&#x2F;opam-overlays.git#2a9543286ff0e0656058fee5c0da7abc16b8717d))
&lt;&#x2F;span&gt;&lt;span&gt;   ((source
&lt;&#x2F;span&gt;&lt;span style=&quot;background-color:#ffecec;color:#323232;&quot;&gt;-    https:&#x2F;&#x2F;github.com&#x2F;alicecaml&#x2F;alice-opam-repo#9957c6334ca7ea18a973ea2fa9e3e56ab9c85eeb))))
&lt;&#x2F;span&gt;&lt;span style=&quot;background-color:#eaffea;font-weight:bold;color:#55a532;&quot;&gt;+&lt;&#x2F;span&gt;&lt;span style=&quot;background-color:#eaffea;color:#323232;&quot;&gt;    https:&#x2F;&#x2F;github.com&#x2F;alicecaml&#x2F;alice-opam-repo#781db10863f3b7a3507842e88d0d3beeebd264ad))))
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt; (solved_for_platforms
&lt;&#x2F;span&gt;&lt;span&gt;  ((arch x86_64)
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;font-style:italic;color:#969896;&quot;&gt;@@ -26,4 +26,10 @@
&lt;&#x2F;span&gt;&lt;span&gt;   (sys-ocaml-version 5.3.1+relocatable))
&lt;&#x2F;span&gt;&lt;span&gt;  ((arch arm64)
&lt;&#x2F;span&gt;&lt;span&gt;   (os macos)
&lt;&#x2F;span&gt;&lt;span style=&quot;background-color:#eaffea;font-weight:bold;color:#55a532;&quot;&gt;+&lt;&#x2F;span&gt;&lt;span style=&quot;background-color:#eaffea;color:#323232;&quot;&gt;  (sys-ocaml-version 5.3.1+relocatable))
&lt;&#x2F;span&gt;&lt;span style=&quot;background-color:#eaffea;font-weight:bold;color:#55a532;&quot;&gt;+&lt;&#x2F;span&gt;&lt;span style=&quot;background-color:#eaffea;color:#323232;&quot;&gt; ((arch x86_64)
&lt;&#x2F;span&gt;&lt;span style=&quot;background-color:#eaffea;font-weight:bold;color:#55a532;&quot;&gt;+&lt;&#x2F;span&gt;&lt;span style=&quot;background-color:#eaffea;color:#323232;&quot;&gt;  (os win32)
&lt;&#x2F;span&gt;&lt;span style=&quot;background-color:#eaffea;font-weight:bold;color:#55a532;&quot;&gt;+&lt;&#x2F;span&gt;&lt;span style=&quot;background-color:#eaffea;color:#323232;&quot;&gt;  (sys-ocaml-version 5.3.1+relocatable))
&lt;&#x2F;span&gt;&lt;span style=&quot;background-color:#eaffea;font-weight:bold;color:#55a532;&quot;&gt;+&lt;&#x2F;span&gt;&lt;span style=&quot;background-color:#eaffea;color:#323232;&quot;&gt; ((arch arm64)
&lt;&#x2F;span&gt;&lt;span style=&quot;background-color:#eaffea;font-weight:bold;color:#55a532;&quot;&gt;+&lt;&#x2F;span&gt;&lt;span style=&quot;background-color:#eaffea;color:#323232;&quot;&gt;  (os win32)
&lt;&#x2F;span&gt;&lt;span&gt;   (sys-ocaml-version 5.3.1+relocatable)))
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Trying to build again:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;PS D:\src\alice&amp;gt; dune build
&lt;&#x2F;span&gt;&lt;span&gt;    Building ocaml-system.5.3.1+relocatable
&lt;&#x2F;span&gt;&lt;span&gt;    Building ocaml-config.3
&lt;&#x2F;span&gt;&lt;span&gt;    Building ocaml.5.3.1+relocatable
&lt;&#x2F;span&gt;&lt;span&gt; Downloading xdg.3.19.1
&lt;&#x2F;span&gt;&lt;span&gt;    Building xdg.3.19.1
&lt;&#x2F;span&gt;&lt;span&gt;    Building base-unix.base
&lt;&#x2F;span&gt;&lt;span&gt;Shared cache miss [bc92329a7327ee6a16f45fa75edf32e5] (_build&#x2F;_fetch&#x2F;checksum&#x2F;md5=a460f01d409d51b7d537429881bfa276&#x2F;dir): error: Unix.Unix_error(Unix.EXDEV, &amp;quot;link&amp;quot;, &amp;quot;_build&#x2F;_fetch&#x2F;checksum&#x2F;md5=a460f01d409d51b7d537429881bfa276&#x2F;dir&#x2F;.gitignore&amp;quot;)
&lt;&#x2F;span&gt;&lt;span&gt; Downloading ISO8601.0.2.6
&lt;&#x2F;span&gt;&lt;span&gt;    Building ISO8601.0.2.6
&lt;&#x2F;span&gt;&lt;span&gt; Downloading menhirLib.20240715
&lt;&#x2F;span&gt;&lt;span&gt; Downloading menhirSdk.20240715
&lt;&#x2F;span&gt;&lt;span&gt; Downloading menhirCST.20240715
&lt;&#x2F;span&gt;&lt;span&gt;    Building menhirLib.20240715
&lt;&#x2F;span&gt;&lt;span&gt;    Building menhirSdk.20240715
&lt;&#x2F;span&gt;&lt;span&gt;    Building menhirCST.20240715
&lt;&#x2F;span&gt;&lt;span&gt; Downloading menhir.20240715
&lt;&#x2F;span&gt;&lt;span&gt;    Building menhir.20240715
&lt;&#x2F;span&gt;&lt;span&gt;Shared cache miss [919bb687d0058dc8265afa905483f2bd] (_build&#x2F;_fetch&#x2F;checksum&#x2F;sha256=1d4e9c16ed9e24d46dd757ce94adc7fc8b2068eb5ff7cd2a70fce08135a752ef&#x2F;dir): error: Unix.Unix_error(Unix.EXDEV, &amp;quot;link&amp;quot;, &amp;quot;_build&#x2F;_fetch&#x2F;checksum&#x2F;sha256=1d4e9c16ed9e24d46dd757ce94adc7fc8b2068eb5ff7cd2a70fce08135a752ef&#x2F;dir&#x2F;.gitignore&amp;quot;)
&lt;&#x2F;span&gt;&lt;span&gt; Downloading toml.7.1.0
&lt;&#x2F;span&gt;&lt;span&gt;    Building toml.7.1.0
&lt;&#x2F;span&gt;&lt;span&gt; Downloading stdlib-shims.0.3.0
&lt;&#x2F;span&gt;&lt;span&gt;    Building stdlib-shims.0.3.0
&lt;&#x2F;span&gt;&lt;span&gt; Downloading sha.1.15.4
&lt;&#x2F;span&gt;&lt;span&gt;    Building sha.1.15.4
&lt;&#x2F;span&gt;&lt;span&gt;    Building seq.base
&lt;&#x2F;span&gt;&lt;span&gt; Downloading re.1.13.2
&lt;&#x2F;span&gt;&lt;span&gt;    Building re.1.13.2
&lt;&#x2F;span&gt;&lt;span&gt;Shared cache miss [806d141e2f359f01b7662634ccd5886d] (_build&#x2F;_fetch&#x2F;checksum&#x2F;sha256=796d5791e2bf7b3bff200cf5057a7a1878439ebcd74ed0f1088cf86756d52be6&#x2F;dir): error: Unix.Unix_error(Unix.EXDEV, &amp;quot;link&amp;quot;, &amp;quot;_build&#x2F;_fetch&#x2F;checksum&#x2F;sha256=796d5791e2bf7b3bff200cf5057a7a1878439ebcd74ed0f1088cf86756d52be6&#x2F;dir&#x2F;.gitignore&amp;quot;)
&lt;&#x2F;span&gt;&lt;span&gt; Downloading fileutils.0.6.6
&lt;&#x2F;span&gt;&lt;span&gt;    Building fileutils.0.6.6
&lt;&#x2F;span&gt;&lt;span&gt; Downloading ordering.3.19.1
&lt;&#x2F;span&gt;&lt;span&gt; Downloading pp.2.0.0
&lt;&#x2F;span&gt;&lt;span&gt;    Building pp.2.0.0
&lt;&#x2F;span&gt;&lt;span&gt;    Building ordering.3.19.1
&lt;&#x2F;span&gt;&lt;span&gt; Downloading dyn.3.19.1
&lt;&#x2F;span&gt;&lt;span&gt;    Building dyn.3.19.1
&lt;&#x2F;span&gt;&lt;span&gt; Downloading climate.0.8.0
&lt;&#x2F;span&gt;&lt;span&gt;    Building climate.0.8.0
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;That worked!&lt;&#x2F;p&gt;
&lt;p&gt;I thought I might have to apply some of the hacks I wrote about while getting
&lt;code&gt;ocamlformat&lt;&#x2F;code&gt; and &lt;code&gt;ocamllsp&lt;&#x2F;code&gt; to build
(&lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;alicecaml&#x2F;alice&#x2F;blob&#x2F;1c934ce6eb096130659268e913e4da54b7b2853c&#x2F;tool-build-scripts&#x2F;5.3.1&#x2F;windows-notes.md&quot;&gt;here&lt;&#x2F;a&gt;)
but fortunately not. Most of those problems come from the dependency on packages
that don’t use Dune as their build system, and I’ve avoided such packages in
Alice.&lt;&#x2F;p&gt;
&lt;p&gt;The &lt;code&gt;Shared cache miss&lt;&#x2F;code&gt; errors are benign, and I think related to
the fact that my user account in on a different partition to the project I’m
building, though it’s odd that it doesn’t happen for all the dependencies.&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;PS D:\src\alice&amp;gt; ls .\_build\default\alice\src\alice.exe
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    Directory: D:\src\alice\_build\default\alice\src
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;Mode                 LastWriteTime         Length Name
&lt;&#x2F;span&gt;&lt;span&gt;----                 -------------         ------ ----
&lt;&#x2F;span&gt;&lt;span&gt;-ar---        2025-08-28     02:51        7180768 alice.exe
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;PS D:\src\alice&amp;gt; ls .\_build\install\default\bin\
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    Directory: D:\src\alice\_build\install\default\bin
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;Mode                 LastWriteTime         Length Name
&lt;&#x2F;span&gt;&lt;span&gt;----                 -------------         ------ ----
&lt;&#x2F;span&gt;&lt;span&gt;-ar---        2025-08-28     02:51        7180768 alice.exe
&lt;&#x2F;span&gt;&lt;span&gt;-ar---        2025-08-28     02:51        4825269 alice_demo.exe
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;One difference I observe between building Alice on Windows verses Unix-based
OSes is that on Windows it’s very slow to compile. It took over 7 minutes.
One possibility is that I’m building Alice from a spinning disk drive. I use
this same computer for most of my OCaml development but usually booted into
Linux and I don’t remember if I use an SSD or spinning disk for that work but
I’ve never run into this type of performance issue there.&lt;&#x2F;p&gt;
&lt;p&gt;The &lt;code&gt;Shared cache miss&lt;&#x2F;code&gt; errors suggest something is going wrong accessing Dune’s
cache, and I see that rebuilding the project causes dependencies to be
downloaded again which also suggests a cache problem. As an experiment I deleted
the Dune cache from my Mac (a 2020 Macbook Air) and rebuilt Alice there, and from a
cold cache it took about a minute compared to 10 seconds building from scratch
on my Mac from a warm cache, so the caching issue on Windows probably has an
impact on the build time but it’s clearly not the full story.&lt;&#x2F;p&gt;
&lt;p&gt;I tried copying the project into my Windows user account which &lt;em&gt;is&lt;&#x2F;em&gt; on an SSD, and the
same partition as Dune’s cache. Rebuilding from a clean project with a cold
cache took almost 9 minutes this time but there were no cache errors. I think
this rules out the explanation that my spinning disk is the problem. Rebuilding
from a clean project a second time still caused the dependencies to be
re-downloaded so I’m not sure if the cache is even enabled (but then why the
cache errors when I was on a different drive?).&lt;&#x2F;p&gt;
&lt;p&gt;Anyway incremental builds are still pretty fast and that’s
all I’ll be needing today so I’ll move on. The goal of this project isn’t to
debug Dune performance issues on Windows.&lt;&#x2F;p&gt;
&lt;p&gt;Now that I have Alice building on Windows the next step is to make sure
&lt;code&gt;ocamlformat&lt;&#x2F;code&gt; and &lt;code&gt;ocamllsp&lt;&#x2F;code&gt; work and integrate them into my editor. I built
binary versions of these tools for Windows in preparation for today’s work but I
didn’t test them yet.&lt;&#x2F;p&gt;
&lt;p&gt;I’m going to be using Neovim with the same
&lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;gridbugs&#x2F;dotfiles&#x2F;tree&#x2F;main&#x2F;nvim&quot;&gt;configuration&lt;&#x2F;a&gt; as I use
for development on Linux and macOS. It starts an &lt;code&gt;ocamllsp&lt;&#x2F;code&gt; server upon opening
an OCaml source file and autoformats the code with &lt;code&gt;ocamlformat&lt;&#x2F;code&gt; whenever I
save an OCaml source file.&lt;&#x2F;p&gt;
&lt;p&gt;Both of these tools worked flawlessly on the first try.&lt;&#x2F;p&gt;
&lt;p&gt;The next step is to add Windows support to the &lt;code&gt;alice tools get&lt;&#x2F;code&gt; command which
downloads the OCaml development tools for the current platform. Alice currently
has no concept of Windows at all, and running that command on my machine prints
&lt;code&gt;Unknown system: MSYS_NT-10.0-22631&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;When Alice installs tools it creates a “root” which is a directory resembling a
typical Unix filesystem root, with subdirectories like &lt;code&gt;bin&lt;&#x2F;code&gt; and &lt;code&gt;share&lt;&#x2F;code&gt;. This
lets it include things like manual pages when installing tools as well as the
tools themselves. Over time I expect to support multiple different versions of the compiler
though currently &lt;code&gt;5.3.1+relocatable&lt;&#x2F;code&gt; is the only supported version. Similar
to &lt;a href=&quot;https:&#x2F;&#x2F;rustup.rs&#x2F;&quot;&gt;&lt;code&gt;rustup&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; it’s possible to change the
global root that is considered “active” by running the command &lt;code&gt;alice tools change&lt;&#x2F;code&gt;. This creates a symlink at &lt;code&gt;~&#x2F;.alice&#x2F;current&lt;&#x2F;code&gt; pointing to (say)
&lt;code&gt;~&#x2F;.alice&#x2F;roots&#x2F;5.3.1+relocatable&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;The problem is that creating a symlink on Windows requires admin permissions.
My solution is to copy the selected root into &lt;code&gt;~&#x2F;.alice&#x2F;current&lt;&#x2F;code&gt; instead.&lt;&#x2F;p&gt;
&lt;p&gt;Now it works:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;PS D:\src\alice&amp;gt; alice tools get
&lt;&#x2F;span&gt;&lt;span&gt;Fetching ocaml.5.3.1+relocatable...Done!
&lt;&#x2F;span&gt;&lt;span&gt;Unpacking ocaml.5.3.1+relocatable...Done!
&lt;&#x2F;span&gt;&lt;span&gt;Successfully installed ocaml.5.3.1+relocatable!
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;Fetching ocamllsp.1.22.0...Done!
&lt;&#x2F;span&gt;&lt;span&gt;Unpacking ocamllsp.1.22.0...Done!
&lt;&#x2F;span&gt;&lt;span&gt;Successfully installed ocamllsp.1.22.0!
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;Fetching ocamlformat.0.27.0...Done!
&lt;&#x2F;span&gt;&lt;span&gt;Unpacking ocamlformat.0.27.0...Done!
&lt;&#x2F;span&gt;&lt;span&gt;Successfully installed ocamlformat.0.27.0!
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;No current root was found so making 5.3.1+relocatable the current root.
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Even though that command installed the same version of the tools I’m already
using, I updated my &lt;code&gt;PATH&lt;&#x2F;code&gt; variable to include &lt;code&gt;$HOME\.alice\current\bin&lt;&#x2F;code&gt;,
so now I have:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;PS D:\src\alice&amp;gt; Get-Command ocamlopt
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;CommandType     Name          Version    Source
&lt;&#x2F;span&gt;&lt;span&gt;-----------     ----          -------    ------
&lt;&#x2F;span&gt;&lt;span&gt;Application     ocamlopt.exe  0.0.0.0    C:\Users\steph\.alice\current\bin\ocamlopt.exe
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Next up let’s see if it’s possible to create a new project using Alice on
Windows:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;PS D:\tmp&amp;gt; alice new foo
&lt;&#x2F;span&gt;&lt;span&gt;Created new executable project in D:\tmp\foo
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;PS D:\tmp&amp;gt; find foo
&lt;&#x2F;span&gt;&lt;span&gt;foo
&lt;&#x2F;span&gt;&lt;span&gt;foo&#x2F;.gitignore
&lt;&#x2F;span&gt;&lt;span&gt;foo&#x2F;Alice.toml
&lt;&#x2F;span&gt;&lt;span&gt;foo&#x2F;src
&lt;&#x2F;span&gt;&lt;span&gt;foo&#x2F;src&#x2F;main.ml
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Can we build it?&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;PS D:\tmp\foo&amp;gt; alice build
&lt;&#x2F;span&gt;&lt;span&gt;Program &amp;quot;ocamldep.opt&amp;quot; not found!
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;I do have &lt;code&gt;ocamldep.opt&lt;&#x2F;code&gt; installed as part of the OCaml compiler toolchain
however I suspect the &lt;code&gt;.exe&lt;&#x2F;code&gt; extension is causing the problem:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;PS D:\tmp\foo&amp;gt; Get-Command ocamldep.opt
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;CommandType Name             Version Source
&lt;&#x2F;span&gt;&lt;span&gt;----------- ----             ------- ------
&lt;&#x2F;span&gt;&lt;span&gt;Application ocamldep.opt.exe 0.0.0.0 C:\Users\steph\.alice\current\bin\ocamldep.opt.exe
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;I updated Alice to include the &lt;code&gt;.exe&lt;&#x2F;code&gt; on Windows. I also needed to update the
invocation of &lt;code&gt;ocamlopt.opt&lt;&#x2F;code&gt; to &lt;code&gt;ocamlopt.opt.exe&lt;&#x2F;code&gt;. After this change
&lt;code&gt;alice build&lt;&#x2F;code&gt; could run successfully.&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;PS D:\tmp\foo&amp;gt; alice build
&lt;&#x2F;span&gt;&lt;span&gt;PS D:\tmp\foo&amp;gt; ls .\build\
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    Directory: D:\tmp\foo\build
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;Mode                 LastWriteTime         Length Name
&lt;&#x2F;span&gt;&lt;span&gt;----                 -------------         ------ ----
&lt;&#x2F;span&gt;&lt;span&gt;-a----        2025-08-29     10:25        3134570 foo.exe
&lt;&#x2F;span&gt;&lt;span&gt;-a----        2025-08-29     10:25            180 main.cmi
&lt;&#x2F;span&gt;&lt;span&gt;-a----        2025-08-29     10:25            196 main.cmx
&lt;&#x2F;span&gt;&lt;span&gt;-a----        2025-08-29     10:18             38 main.ml
&lt;&#x2F;span&gt;&lt;span&gt;-a----        2025-08-29     10:25           1226 main.o
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;PS D:\tmp\foo&amp;gt; .\build\foo.exe
&lt;&#x2F;span&gt;&lt;span&gt;Hello, World!
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Alice has some commands for cleaning and running a project too:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;PS D:\tmp\foo&amp;gt; alice clean
&lt;&#x2F;span&gt;&lt;span&gt;PS D:\tmp\foo&amp;gt; alice run
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Need to add some more printouts so it’s more clear when a command completes
successfully! In the case of &lt;code&gt;alice run&lt;&#x2F;code&gt; something did go wrong since it didn’t
print “Hello, World!”. When I press enter again I see “Hello, World!” print at
the next prompt:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;PS D:\tmp\foo&amp;gt; alice run
&lt;&#x2F;span&gt;&lt;span&gt;PS D:\tmp\foo&amp;gt; Hello, World!
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Alice runs programs with &lt;code&gt;Unix.execv&lt;&#x2F;code&gt;. Here’s its documentation:&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;execv prog args&lt;&#x2F;code&gt; execute the program in file &lt;code&gt;prog&lt;&#x2F;code&gt;, with the arguments &lt;code&gt;args&lt;&#x2F;code&gt;, and the current process environment. Note that the first argument, &lt;code&gt;args.(0)&lt;&#x2F;code&gt;, is by convention the filename of the program being executed, just like &lt;code&gt;Sys.argv.(0)&lt;&#x2F;code&gt;. These &lt;code&gt;execv*&lt;&#x2F;code&gt; functions never return: on success, the current program is replaced by the new one.&lt;&#x2F;p&gt;
&lt;p&gt;On Windows: the CRT simply spawns a new process and exits the current one. This will have unwanted consequences if e.g. another process is waiting on the current one. Using &lt;code&gt;create_process&lt;&#x2F;code&gt; or one of the &lt;code&gt;open_process_*&lt;&#x2F;code&gt; functions instead is recommended.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;@raise&lt;&#x2F;strong&gt;&lt;&#x2F;em&gt; &lt;code&gt;Unix_error&lt;&#x2F;code&gt; on failure&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;As the docs suggest I replaced &lt;code&gt;Unix.execv&lt;&#x2F;code&gt; with &lt;code&gt;Unix.create_process&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;PS D:\tmp\foo&amp;gt; alice run
&lt;&#x2F;span&gt;&lt;span&gt;Hello, World!
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Next I want to test &lt;code&gt;alice dot&lt;&#x2F;code&gt; which prints the graphviz dot sourcecode for the
build graph:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;PS D:\tmp\foo&amp;gt; alice dot
&lt;&#x2F;span&gt;&lt;span&gt;digraph {
&lt;&#x2F;span&gt;&lt;span&gt;  &amp;quot;foo&amp;quot; -&amp;gt; {&amp;quot;main.cmx&amp;quot;, &amp;quot;main.o&amp;quot;}
&lt;&#x2F;span&gt;&lt;span&gt;  &amp;quot;main.cmx&amp;quot; -&amp;gt; {&amp;quot;main.ml&amp;quot;}
&lt;&#x2F;span&gt;&lt;span&gt;  &amp;quot;main.o&amp;quot; -&amp;gt; {&amp;quot;main.ml&amp;quot;}
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The &lt;code&gt;.exe&lt;&#x2F;code&gt; is missing from the executable name. Windows obviously allows file
extensions like &lt;code&gt;.exe&lt;&#x2F;code&gt; to be omitted under some conditions since &lt;code&gt;alice run&lt;&#x2F;code&gt; was
able to execute &lt;code&gt;foo.exe&lt;&#x2F;code&gt; without its extension. It would still be better to
include the file extension here and in any other debug output printed by Alice.
Another place where the extension is missing is from the build commands
themselves. Rerunning the build with verbose logging:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;PS D:\tmp\foo&amp;gt; alice build -vv
&lt;&#x2F;span&gt;&lt;span&gt;[DEBUG] copying source file: main.ml
&lt;&#x2F;span&gt;&lt;span&gt;[DEBUG] running build command: ocamlopt.opt.exe -g -c main.ml
&lt;&#x2F;span&gt;&lt;span&gt;[DEBUG] running build command: ocamlopt.opt.exe -g -o foo main.cmx
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The OCaml compiler is clearly able to add the appropriate extension even though
it was omitted from &lt;code&gt;-o foo&lt;&#x2F;code&gt; but it would still be better to update the build
rules so that on Windows they use the correct extension for executable files.&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;PS D:\tmp\foo&amp;gt; alice build -vv
&lt;&#x2F;span&gt;&lt;span&gt;[DEBUG] copying source file: main.ml
&lt;&#x2F;span&gt;&lt;span&gt;[DEBUG] running build command: ocamlopt.opt.exe -g -c main.ml
&lt;&#x2F;span&gt;&lt;span&gt;[DEBUG] running build command: ocamlopt.opt.exe -g -o foo.exe main.cmx
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;PS D:\tmp\foo&amp;gt; alice dot
&lt;&#x2F;span&gt;&lt;span&gt;digraph {
&lt;&#x2F;span&gt;&lt;span&gt;  &amp;quot;foo.exe&amp;quot; -&amp;gt; {&amp;quot;main.cmx&amp;quot;, &amp;quot;main.o&amp;quot;}
&lt;&#x2F;span&gt;&lt;span&gt;  &amp;quot;main.cmx&amp;quot; -&amp;gt; {&amp;quot;main.ml&amp;quot;}
&lt;&#x2F;span&gt;&lt;span&gt;  &amp;quot;main.o&amp;quot; -&amp;gt; {&amp;quot;main.ml&amp;quot;}
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Just for fun here’s a render of the graph made with &lt;code&gt;alice dot | dot -Tsvg &amp;gt; graph.svg&lt;&#x2F;code&gt;
showing dependencies between build artifacts and source files.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;porting-my-toy-ocaml-build-system-to-windows&#x2F;graph.svg&quot; alt=&quot;A directed graph with nodes labeled with file names and edges representing build dependencies&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;I added a second source file and interface to exercise Alice on a slightly less
trivial case:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;PS D:\tmp\foo&amp;gt; alice build -vv
&lt;&#x2F;span&gt;&lt;span&gt;[DEBUG] copying source file: foo.mli
&lt;&#x2F;span&gt;&lt;span&gt;[DEBUG] running build command: ocamlopt.opt.exe -g -c foo.mli
&lt;&#x2F;span&gt;&lt;span&gt;[DEBUG] copying source file: foo.ml
&lt;&#x2F;span&gt;&lt;span&gt;[DEBUG] running build command: ocamlopt.opt.exe -g -c foo.ml
&lt;&#x2F;span&gt;&lt;span&gt;[DEBUG] copying source file: main.ml
&lt;&#x2F;span&gt;&lt;span&gt;[DEBUG] running build command: ocamlopt.opt.exe -g -c main.ml
&lt;&#x2F;span&gt;&lt;span&gt;[DEBUG] running build command: ocamlopt.opt.exe -g -o foo.exe foo.cmx main.cmx
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;porting-my-toy-ocaml-build-system-to-windows&#x2F;graph2.svg&quot; alt=&quot;A directed graph with nodes labeled with file names and edges representing build dependencies&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;I did a little more work not directly relating to supporting Windows but which
will make it easier for Alice to find the OCaml compiler, assuming the toolchain
was installed by Alice. If Alice would run an external program (such as the
OCaml compiler) and the program isn’t in the user’s &lt;code&gt;PATH&lt;&#x2F;code&gt; variable then Alice
will run the program from the current root (like &lt;code&gt;~&#x2F;.alice&#x2F;current&lt;&#x2F;code&gt;). This
will make Alice easier to set up since it removes the need to add
&lt;code&gt;~&#x2F;.alice&#x2F;current&#x2F;bin&lt;&#x2F;code&gt; to &lt;code&gt;PATH&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;And now Alice works on Windows!&lt;&#x2F;p&gt;
&lt;p&gt;You need to be in Powershell rather than CMD.exe, and you’ll need a C compiler
like LLVM installed an in your &lt;code&gt;PATH&lt;&#x2F;code&gt; for the OCaml compiler to work
correctly. After that, as long as &lt;code&gt;alice.exe&lt;&#x2F;code&gt; is in your &lt;code&gt;PATH&lt;&#x2F;code&gt;, just run
run &lt;code&gt;alice tools get&lt;&#x2F;code&gt; to install the OCaml compiler and dev tools, &lt;code&gt;alice new &amp;lt;NAME&amp;gt;&lt;&#x2F;code&gt;
to make a new project, and &lt;code&gt;alice run&lt;&#x2F;code&gt; from within the new project’s directory
to run the project.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;porting-my-toy-ocaml-build-system-to-windows&#x2F;screenshot.png&quot; alt=&quot;A screenshot of a powershell session with the commands get started with Alice&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Alice is highly experimental and far from ready for everyday use. The next step
will be allowing Alice packages to depend on each other.&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Alice the Caml</title>
          <pubDate>Tue, 26 Aug 2025 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/alice-the-caml/</link>
          <guid>https://www.gridbugs.org/alice-the-caml/</guid>
          <description xml:base="https://www.gridbugs.org/alice-the-caml/">&lt;p&gt;&lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;alicecaml&#x2F;alice&quot;&gt;Alice&lt;&#x2F;a&gt; is an experimental OCaml build
system emphasizing accessibility. It’s still very much a work in progress and
nowhere near ready for regular use. Currently it can build multi-file OCaml
projects to executables and libraries. Its UI is inspired by cargo.&lt;&#x2F;p&gt;
&lt;p&gt;I’m one of the core Dune developers for my day job. Dune is a mature and widely
used OCaml build system which it difficult to make large structural changes to
its UI and packaging philosophy. Alice is an experiment exploring the design
space of OCaml build systems when these constraints are lifted.&lt;&#x2F;p&gt;
&lt;p&gt;Here’s a tiny demo:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;$ alice new hello
&lt;&#x2F;span&gt;&lt;span&gt;$ cd hello
&lt;&#x2F;span&gt;&lt;span&gt;$ cat src&#x2F;main.ml
&lt;&#x2F;span&gt;&lt;span&gt;let () = print_endline &amp;quot;Hello, World!&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;$ alice run
&lt;&#x2F;span&gt;&lt;span&gt;Hello, World!
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;It has incremental builds though they are currently sequential. It generates a
build graph which could be executed in parallel but this is future work. Running
&lt;code&gt;alice dot&lt;&#x2F;code&gt; prints a graphviz file representing the build graph.&lt;&#x2F;p&gt;
&lt;p&gt;It uses my &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;gridbugs&#x2F;climate&quot;&gt;climate&lt;&#x2F;a&gt; library for its CLI
and so contains an inbuilt mechanism for generating bash completion scripts.&lt;&#x2F;p&gt;
&lt;p&gt;It doesn’t assume anything about your OCaml compiler setup. You can install the
compiler with opam or nix or your system package manager or manually build it from
source if you like. However it does come with a mechanism for downloading a
precompiled OCaml toolchain and some other tools (an LSP server and code
formatter), avoiding the need for users to build the compiler from source which
can be time-consuming. Precompiled toolchains are available for Linux x86_64,
MacOS x86_64&#x2F;aarch64 and Windows x86_64, though I haven’t yet ported Alice to
Windows. Alice deviates from opam in that the compiler and development tools are
not considered to be packages.&lt;&#x2F;p&gt;
&lt;p&gt;There’s currently no packaging story and packages cannot yet depend on one
another. It would be nice to one day support depending on opam packages but this
is not going to be the default package format and opam compatibility isn’t a
major priority for the project. Instead I’ll design a packaging system
prioritizing ease of publishing and maintenance with as low cognitive load as
possible, and then see how opam compatibility can fit into that picture.&lt;&#x2F;p&gt;
&lt;p&gt;The name comes from an &lt;a href=&quot;https:&#x2F;&#x2F;www.youtube.com&#x2F;watch?v=XM7Jnetdf0I&quot;&gt;Australian children’s
song&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;My next two goals for the project are Windows support and basic package
management.&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;$ alice --help
&lt;&#x2F;span&gt;&lt;span&gt;Alice is a build system for OCaml projects.
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;Usage: alice [COMMAND]
&lt;&#x2F;span&gt;&lt;span&gt;       alice [OPTION]…
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;Options:
&lt;&#x2F;span&gt;&lt;span&gt;  -h, --help  Show this help message.
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;Commands:
&lt;&#x2F;span&gt;&lt;span&gt;  build, b  Build a project.
&lt;&#x2F;span&gt;&lt;span&gt;  clean, c  Delete all generated build artifacts.
&lt;&#x2F;span&gt;&lt;span&gt;  dot       Print graphviz source describing build artifact dependencies.
&lt;&#x2F;span&gt;&lt;span&gt;  new       Create a new alice project.
&lt;&#x2F;span&gt;&lt;span&gt;  tools     Manage tools for building and developing OCaml projects.
&lt;&#x2F;span&gt;&lt;span&gt;  run, r    Build a project and run its executable.
&lt;&#x2F;span&gt;&lt;span&gt;  help      Print documentation for a subcommand.
&lt;&#x2F;span&gt;&lt;span&gt;  internal  Internal commands.
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Read more on &lt;a href=&quot;https:&#x2F;&#x2F;www.alicecaml.org&quot;&gt;Alice’s website&lt;&#x2F;a&gt;!&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Fixing my Old OS Assignment with Machine Code Hacks</title>
          <pubDate>Sun, 24 Aug 2025 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/fixing-my-old-os-assignment-with-machine-code-hacks/</link>
          <guid>https://www.gridbugs.org/fixing-my-old-os-assignment-with-machine-code-hacks/</guid>
          <description xml:base="https://www.gridbugs.org/fixing-my-old-os-assignment-with-machine-code-hacks/">&lt;p&gt;Back in 2012 I took an Operating Systems course, the main assessment of which
was a 12 week pair assignment implementing a user-level OS personality for the
&lt;a href=&quot;https:&#x2F;&#x2F;sel4.systems&#x2F;&quot;&gt;seL4 microkernel&lt;&#x2F;a&gt;. I recently got the urge to boot up
our little OS. I saved the contents of my university computer system home directory
before I graduated so I still have a copy of the code, and
fortunately I didn’t &lt;code&gt;make clean&lt;&#x2F;code&gt; the last time I worked on it so the bootable
(at least in theory!) disk image is still in the project directory.&lt;&#x2F;p&gt;
&lt;p&gt;Also fortunately my project partner somehow forgot to return the hardware loaned
to them by the university and over the following years it ended up in one of my
(ahem) several boxes of junk that I’ll definitely do something with someday.&lt;&#x2F;p&gt;
&lt;p&gt;And for this &lt;a href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;NSLU2&quot;&gt;Storage Link for USB 2.0 Disk Drives&lt;&#x2F;a&gt;, a.k.a NSLU2,
affectionately referred to as the Slug, today was that day.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;fixing-my-old-os-assignment-with-machine-code-hacks&#x2F;nslu2.jpg&quot; alt=&quot;My Network Storage Link for USB 2.0 Disk Drives&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;h2 id=&quot;powering-it-on&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#powering-it-on&quot; aria-label=&quot;Anchor link for: powering-it-on&quot;&gt;Powering it On&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;Plugging it into power and switching it on illuminated the power indicator LED.
There’s no screen or input devices, so all interaction will be via an
aftermarket USB serial port. In the image below the USB serial port is the
left-most port which was clearly made by a power tool.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;fixing-my-old-os-assignment-with-machine-code-hacks&#x2F;back.jpg&quot; alt=&quot;Back of the slug showing the aftermarket serial port&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Plugging it into my computer with a USB cable causes the serial device to show up in the
output of &lt;code&gt;dmesg&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;[138157.363235] usb 1-5: new full-speed USB device number 18 using xhci_hcd
&lt;&#x2F;span&gt;&lt;span&gt;[138157.511474] usb 1-5: New USB device found, idVendor=0403, idProduct=6001, bcdDevice= 6.00
&lt;&#x2F;span&gt;&lt;span&gt;[138157.511481] usb 1-5: New USB device strings: Mfr=1, Product=2, SerialNumber=3
&lt;&#x2F;span&gt;&lt;span&gt;[138157.511483] usb 1-5: Product: USB &amp;lt;-&amp;gt; UNSW-AOS
&lt;&#x2F;span&gt;&lt;span&gt;[138157.511485] usb 1-5: Manufacturer: FTDI
&lt;&#x2F;span&gt;&lt;span&gt;[138157.511487] usb 1-5: SerialNumber: 12345678
&lt;&#x2F;span&gt;&lt;span&gt;[138157.516506] ftdi_sio 1-5:1.0: FTDI USB Serial Device converter detected
&lt;&#x2F;span&gt;&lt;span&gt;[138157.516539] usb 1-5: Detected FT232R
&lt;&#x2F;span&gt;&lt;span&gt;[138157.521742] usb 1-5: FTDI USB Serial Device converter now attached to ttyUSB0
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Running &lt;code&gt;picocom&lt;&#x2F;code&gt; to open a terminal on &lt;code&gt;&#x2F;dev&#x2F;ttyUSB0&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;$ picocom &#x2F;dev&#x2F;ttyUSB0
&lt;&#x2F;span&gt;&lt;span&gt;picocom v2024-07
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;port is        : &#x2F;dev&#x2F;ttyUSB0
&lt;&#x2F;span&gt;&lt;span&gt;flowcontrol    : none
&lt;&#x2F;span&gt;&lt;span&gt;baudrate is    : 9600
&lt;&#x2F;span&gt;&lt;span&gt;parity is      : none
&lt;&#x2F;span&gt;&lt;span&gt;databits are   : 8
&lt;&#x2F;span&gt;&lt;span&gt;stopbits are   : 1
&lt;&#x2F;span&gt;&lt;span&gt;txdelay is     : 0 ns
&lt;&#x2F;span&gt;&lt;span&gt;escape is      : C-a
&lt;&#x2F;span&gt;&lt;span&gt;local echo is  : no
&lt;&#x2F;span&gt;&lt;span&gt;noinit is      : no
&lt;&#x2F;span&gt;&lt;span&gt;noreset is     : no
&lt;&#x2F;span&gt;&lt;span&gt;hangup is      : no
&lt;&#x2F;span&gt;&lt;span&gt;nolock is      : no
&lt;&#x2F;span&gt;&lt;span&gt;send_cmd is    : &#x2F;nix&#x2F;store&#x2F;cmpvp7nx52dmbjaqkiscgkdzqzaikmaz-lrzsz-0.12.20&#x2F;bin&#x2F;sz -vv
&lt;&#x2F;span&gt;&lt;span&gt;receive_cmd is : &#x2F;nix&#x2F;store&#x2F;cmpvp7nx52dmbjaqkiscgkdzqzaikmaz-lrzsz-0.12.20&#x2F;bin&#x2F;rz -vv -E
&lt;&#x2F;span&gt;&lt;span&gt;imap is        :
&lt;&#x2F;span&gt;&lt;span&gt;omap is        :
&lt;&#x2F;span&gt;&lt;span&gt;emap is        : crcrlf,delbs,
&lt;&#x2F;span&gt;&lt;span&gt;logfile is     : none
&lt;&#x2F;span&gt;&lt;span&gt;initstring     : none
&lt;&#x2F;span&gt;&lt;span&gt;exit_after is  : not set
&lt;&#x2F;span&gt;&lt;span&gt;exit is        : no
&lt;&#x2F;span&gt;&lt;span&gt;minimal cmds is: no
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;Type [C-a] [C-h] to see available commands
&lt;&#x2F;span&gt;&lt;span&gt;Terminal ready
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;…but there was no output.&lt;&#x2F;p&gt;
&lt;p&gt;I couldn’t remember the process we used to load our OS project onto the slug. I
was hoping that it would already be loaded on there, and powering it on would
print something to the terminal, but no luck there.&lt;&#x2F;p&gt;
&lt;p&gt;On a whim I searched my entire old uni home directory for “ttyUSB0” since
I assumed the project would be transferred over the serial port and thus I would
have some script that referenced the name of the serial device. This assumption
would later turn out to be false but I did find something helpful in my search:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;$ grep -rni &amp;#39;ttyUSB0&amp;#39;
&lt;&#x2F;span&gt;&lt;span&gt;nslu2-util&#x2F;nslu2.c:67:  char defport[] = &amp;quot;&#x2F;dev&#x2F;ttyUSB0&amp;quot;;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The &lt;code&gt;nslu2-util&lt;&#x2F;code&gt; program is a command-line utility for starting, stopping,
and resetting the slug. Students in the OS course were given a copy of the tool.
It’s a tiny C project with a &lt;code&gt;Makefile&lt;&#x2F;code&gt; and running &lt;code&gt;make&lt;&#x2F;code&gt; built an &lt;code&gt;nslu2&lt;&#x2F;code&gt;
executable:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;$ .&#x2F;nslu2 -h
&lt;&#x2F;span&gt;&lt;span&gt;Usage: nslu2 &amp;lt;-p port&amp;gt; up|down|reset
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;So I ran &lt;code&gt;.&#x2F;nslu2 reset&lt;&#x2F;code&gt; and this caused something to appear on my &lt;code&gt;picocom&lt;&#x2F;code&gt;
serial terminal:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;Terminal ready
&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;R+;#23&#x2F;#766&amp;#39;2&amp;quot;*&amp;quot;&amp;quot;3+&amp;quot;:
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Getting garbage text over a serial port usually means the baudrate is wrong.
The default baudrate was 9600. After trying 115200 instead and resetting the slug
I could read the output:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;$ picocom -b 115200 &#x2F;dev&#x2F;ttyUSB0
&lt;&#x2F;span&gt;&lt;span&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;Terminal ready
&lt;&#x2F;span&gt;&lt;span&gt;++
&lt;&#x2F;span&gt;&lt;span&gt;Ethernet eth0: MAC address 00:14:bf:68:99:03
&lt;&#x2F;span&gt;&lt;span&gt;IP: 192.168.168.2&#x2F;255.255.255.0, Gateway: 192.168.168.1
&lt;&#x2F;span&gt;&lt;span&gt;Default server: 0.0.0.0, DNS server IP: 0.0.0.0
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;RedBoot(tm) bootstrap and debug environment [ROMRAM]
&lt;&#x2F;span&gt;&lt;span&gt;Red Hat certified release, version 1.92 - built 22:30:50, Jul 22 2008
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;Platform: IXDP425 Development Platform (XScale)
&lt;&#x2F;span&gt;&lt;span&gt;Copyright (C) 2000, 2001, 2002, Red Hat, Inc.
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;RAM: 0x00000000-0x02000000, 0x000724b0-0x01ff3000 available
&lt;&#x2F;span&gt;&lt;span&gt;FLASH: 0x50000000 - 0x50800000, 64 blocks of 0x00020000 bytes each.
&lt;&#x2F;span&gt;&lt;span&gt;== Executing boot script in 2.000 seconds - enter ^C to abort
&lt;&#x2F;span&gt;&lt;span&gt;RedBoot&amp;gt; load -r -v -b 0x00100000 -h 192.168.168.1 bootimg.bin;go
&lt;&#x2F;span&gt;&lt;span&gt;Unable to reach host 192.168.168.1 (192.168.168.1)
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h2 id=&quot;redboot&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#redboot&quot; aria-label=&quot;Anchor link for: redboot&quot;&gt;RedBoot&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;The printout indicates that the slug is running a default boot script:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;load -r -v -b 0x00100000 -h 192.168.168.1 bootimg.bin;go
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;I don’t remember interacting with RedBoot as a student, this command must have
once caused our OS project to start running. That script would have been set up by the
people running the OS course; it would be different or absent on a brand new
slug.&lt;&#x2F;p&gt;
&lt;p&gt;The line above is also interesting:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;== Executing boot script in 2.000 seconds - enter ^C to abort
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Resetting the slug and hitting ctrl+c within 2 seconds of seeing that starts an
interactive shell. Luckily this shell accepts a &lt;code&gt;help&lt;&#x2F;code&gt; command. I’ll paste the
entire output here in case it’s helpful to someone in the future:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;RedBoot&amp;gt; help
&lt;&#x2F;span&gt;&lt;span&gt;go to assign mode
&lt;&#x2F;span&gt;&lt;span&gt;   assign
&lt;&#x2F;span&gt;&lt;span&gt;Set&#x2F;Query the system console baud rate
&lt;&#x2F;span&gt;&lt;span&gt;   baudrate [-b &amp;lt;rate&amp;gt;]
&lt;&#x2F;span&gt;&lt;span&gt;sercomm boot flow
&lt;&#x2F;span&gt;&lt;span&gt;   boot
&lt;&#x2F;span&gt;&lt;span&gt;Manage machine caches
&lt;&#x2F;span&gt;&lt;span&gt;   cache [ON | OFF]
&lt;&#x2F;span&gt;&lt;span&gt;Display&#x2F;switch console channel
&lt;&#x2F;span&gt;&lt;span&gt;   channel [-1|&amp;lt;channel number&amp;gt;]
&lt;&#x2F;span&gt;&lt;span&gt;Compute a 32bit checksum [POSIX algorithm] for a range of memory
&lt;&#x2F;span&gt;&lt;span&gt;   cksum -b &amp;lt;location&amp;gt; -l &amp;lt;length&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;Display (hex dump) a range of memory
&lt;&#x2F;span&gt;&lt;span&gt;   dump -b &amp;lt;location&amp;gt; [-l &amp;lt;length&amp;gt;] [-s] [-1|2|4]
&lt;&#x2F;span&gt;&lt;span&gt;Execute an image - with MMU off
&lt;&#x2F;span&gt;&lt;span&gt;   exec [-w timeout] [-b &amp;lt;load addr&amp;gt; [-l &amp;lt;length&amp;gt;]]
&lt;&#x2F;span&gt;&lt;span&gt;        [-r &amp;lt;ramdisk addr&amp;gt; [-s &amp;lt;ramdisk length&amp;gt;]]
&lt;&#x2F;span&gt;&lt;span&gt;        [-c &amp;quot;kernel command line&amp;quot;] [&amp;lt;entry_point&amp;gt;]
&lt;&#x2F;span&gt;&lt;span&gt;Manage FLASH images
&lt;&#x2F;span&gt;&lt;span&gt;   fis {cmds}
&lt;&#x2F;span&gt;&lt;span&gt;Execute code at a location
&lt;&#x2F;span&gt;&lt;span&gt;   go [-w &amp;lt;timeout&amp;gt;] [entry]
&lt;&#x2F;span&gt;&lt;span&gt;Help about help?
&lt;&#x2F;span&gt;&lt;span&gt;   help [&amp;lt;topic&amp;gt;]
&lt;&#x2F;span&gt;&lt;span&gt;Set&#x2F;change IP addresses
&lt;&#x2F;span&gt;&lt;span&gt;   ip_address [-l &amp;lt;local_ip_address&amp;gt;] [-h &amp;lt;server_address&amp;gt;]
&lt;&#x2F;span&gt;&lt;span&gt;Load a file
&lt;&#x2F;span&gt;&lt;span&gt;   load [-r] [-v] [-d] [-h &amp;lt;host&amp;gt;] [-m &amp;lt;varies&amp;gt;] [-c &amp;lt;channel_number&amp;gt;]
&lt;&#x2F;span&gt;&lt;span&gt;        [-b &amp;lt;base_address&amp;gt;] &amp;lt;file_name&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;Compare two blocks of memory
&lt;&#x2F;span&gt;&lt;span&gt;   mcmp -s &amp;lt;location&amp;gt; -d &amp;lt;location&amp;gt; -l &amp;lt;length&amp;gt; [-1|-2|-4]
&lt;&#x2F;span&gt;&lt;span&gt;Fill a block of memory with a pattern
&lt;&#x2F;span&gt;&lt;span&gt;   mfill -b &amp;lt;location&amp;gt; -l &amp;lt;length&amp;gt; -p &amp;lt;pattern&amp;gt; [-1|-2|-4]
&lt;&#x2F;span&gt;&lt;span&gt;move kernel&amp;amp;ramdisk to ram
&lt;&#x2F;span&gt;&lt;span&gt;   move
&lt;&#x2F;span&gt;&lt;span&gt;Network connectivity test
&lt;&#x2F;span&gt;&lt;span&gt;   ping [-v] [-n &amp;lt;count&amp;gt;] [-l &amp;lt;length&amp;gt;] [-t &amp;lt;timeout&amp;gt;] [-r &amp;lt;rate&amp;gt;]
&lt;&#x2F;span&gt;&lt;span&gt;        [-i &amp;lt;IP_addr&amp;gt;] -h &amp;lt;IP_addr&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;Reset the system
&lt;&#x2F;span&gt;&lt;span&gt;   reset
&lt;&#x2F;span&gt;&lt;span&gt;Set&#x2F;Read MAC address for NPE ethernet ports
&lt;&#x2F;span&gt;&lt;span&gt;   set_npe_mac [-p &amp;lt;portnum&amp;gt;] [xx:xx:xx:xx:xx:xx]
&lt;&#x2F;span&gt;&lt;span&gt;go to upgrade mode
&lt;&#x2F;span&gt;&lt;span&gt;   upgrade
&lt;&#x2F;span&gt;&lt;span&gt;Display RedBoot version information
&lt;&#x2F;span&gt;&lt;span&gt;   version
&lt;&#x2F;span&gt;&lt;span&gt;Display (hex dump) a range of memory
&lt;&#x2F;span&gt;&lt;span&gt;   x -b &amp;lt;location&amp;gt; [-l &amp;lt;length&amp;gt;] [-s] [-1|2|4]
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This tells us that the default boot script is running the &lt;code&gt;load&lt;&#x2F;code&gt; command to download the OS
image over the network and then the &lt;code&gt;go&lt;&#x2F;code&gt; command to boot it. This unlocked a memory of
running some software (I forget exactly what) on my laptop to serve the OS image
over the network and plugging the slug directly into my laptop with an Ethernet
cable. Based on the &lt;code&gt;load -r -v -b 0x00100000 -h 192.168.168.1 bootimg.bin&lt;&#x2F;code&gt;
command, in this setup my laptop would have had the IP address &lt;code&gt;192.168.168.1&lt;&#x2F;code&gt;,
and the bootable OS image was in a file named &lt;code&gt;bootimg.bin&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Searching my old home directory for a &lt;code&gt;bootimg.bin&lt;&#x2F;code&gt; and fortunately I still have one:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;$  find . -name bootimg.bin
&lt;&#x2F;span&gt;&lt;span&gt;.&#x2F;aos&#x2F;aoshg&#x2F;images&#x2F;bootimg.bin
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;But the &lt;code&gt;192.168.168.1&lt;&#x2F;code&gt; address isn’t going to work. My home network uses
addresses in the &lt;code&gt;192.168.1.*&lt;&#x2F;code&gt; range, so unless I set up a computer with a spare
Ethernet port as a router and plug the slug directly into it, I’m going to have
to change the network configuration of the slug. Fortunately there’s a command
for that:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;Set&#x2F;change IP addresses
&lt;&#x2F;span&gt;&lt;span&gt;   ip_address [-l &amp;lt;local_ip_address&amp;gt;] [-h &amp;lt;server_address&amp;gt;]
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Running &lt;code&gt;ip_address&lt;&#x2F;code&gt; with no arguments prints the current configuration:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;RedBoot&amp;gt; ip_addr
&lt;&#x2F;span&gt;&lt;span&gt;IP: 192.168.168.2&#x2F;255.255.255.0, Gateway: 192.168.168.1
&lt;&#x2F;span&gt;&lt;span&gt;Default server: 0.0.0.0, DNS server IP: 0.0.0.0
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;And I can change the address with:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;RedBoot&amp;gt; ip_addr -l 192.168.1.22
&lt;&#x2F;span&gt;&lt;span&gt;IP: 192.168.1.22&#x2F;255.255.255.0, Gateway: 192.168.168.1
&lt;&#x2F;span&gt;&lt;span&gt;Default server: 0.0.0.0, DNS server IP: 0.0.0.0
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;I chose &lt;code&gt;192.168.1.22&lt;&#x2F;code&gt; arbitrarily among the unused IP addresses on my home
network. After this change I could &lt;code&gt;ping&lt;&#x2F;code&gt; that address from my computer, and
also use RedBoot’s own &lt;code&gt;ping&lt;&#x2F;code&gt; command to ping my computer (whose address is
&lt;code&gt;192.168.1.7&lt;&#x2F;code&gt;):&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;RedBoot&amp;gt; ping -h 192.168.1.7
&lt;&#x2F;span&gt;&lt;span&gt;Network PING - from 192.168.1.22 to 192.168.1.7
&lt;&#x2F;span&gt;&lt;span&gt;PING - received 10 of 10 expected
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;So the slug is on my home network now and it can see my development machine
where &lt;code&gt;bootimg.bin&lt;&#x2F;code&gt; is located. Now I just need a way to send &lt;code&gt;bootimg.bin&lt;&#x2F;code&gt; over
the network to the slug. The documentation printed by &lt;code&gt;help&lt;&#x2F;code&gt; about the &lt;code&gt;load&lt;&#x2F;code&gt;
command doesn’t go into much detail about what network protocol RedBoot will use
to transfer a file:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;Load a file
&lt;&#x2F;span&gt;&lt;span&gt;   load [-r] [-v] [-d] [-h &amp;lt;host&amp;gt;] [-m &amp;lt;varies&amp;gt;] [-c &amp;lt;channel_number&amp;gt;]
&lt;&#x2F;span&gt;&lt;span&gt;        [-b &amp;lt;base_address&amp;gt;] &amp;lt;file_name&amp;gt;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;I found some better documentation
&lt;a href=&quot;https:&#x2F;&#x2F;doc.ecoscentric.com&#x2F;ref&#x2F;download-command.html&quot;&gt;online&lt;&#x2F;a&gt; which explained
that the &lt;code&gt;-m&lt;&#x2F;code&gt; option accepts &lt;code&gt;TFTP&lt;&#x2F;code&gt; or &lt;code&gt;HTTP&lt;&#x2F;code&gt;. There’s no way to
specify the port number. For some reason this caused me to prefer
&lt;a href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;Trivial_File_Transfer_Protocol&quot;&gt;TFTP&lt;&#x2F;a&gt;, possibly
because I’m so used to HTTP servers running on ports besides the default (80)
when running websites locally, but not so for TFTP. I actually implemented a
&lt;a href=&quot;https:&#x2F;&#x2F;crates.io&#x2F;crates&#x2F;tftp-ro&quot;&gt;simple send-only TFTP server&lt;&#x2F;a&gt; a few years
ago when I got frustrated that all the TFTP servers I could find wouldn’t let
me just serve files out of the current directory, so that’s what I’ll be using
here.&lt;&#x2F;p&gt;
&lt;p&gt;To install it:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;$ cargo install tftp-ro
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;And then I ran it from the directory containing &lt;code&gt;bootimg.bin&lt;&#x2F;code&gt; like:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;$ sudo tftp-ro -v -d .
&lt;&#x2F;span&gt;&lt;span&gt;[2025-08-24T07:46:46Z INFO  tftp_ro] Listening on 0.0.0.0:69
&lt;&#x2F;span&gt;&lt;span&gt;[2025-08-24T07:46:46Z INFO  tftp_ro] Serving files out of .
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Then I tried downloading the file to the slug:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;RedBoot&amp;gt; load -r -v -b 0x00100000 -h 192.168.1.7 -m TFTP bootimg.bin
&lt;&#x2F;span&gt;&lt;span&gt;Can&amp;#39;t load &amp;#39;bootimg.bin&amp;#39;: operation timed out
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;I’m not sure if this is a bug or performance issue with my TFTP server but my
quick fix was to compress the image. RedBoot’s &lt;code&gt;load&lt;&#x2F;code&gt; command takes a flag &lt;code&gt;-d&lt;&#x2F;code&gt;
which decompresses the image assuming it’s compressed with gzip.&lt;&#x2F;p&gt;
&lt;p&gt;So I compressed the image:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;$ gzip -k bootimg.bin
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;…and tried &lt;code&gt;load&lt;&#x2F;code&gt;-ing it again:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;RedBoot&amp;gt; load -r -v -d -b 0x00100000 -h 192.168.1.7 -m TFTP bootimg.bin.gz
&lt;&#x2F;span&gt;&lt;span&gt;\
&lt;&#x2F;span&gt;&lt;span&gt;Raw file loaded 0x00100000-0x0017bfff, assumed entry at 0x00100000
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Success! RedBoot remembers that we loaded that image starting at address
&lt;code&gt;0x00100000&lt;&#x2F;code&gt;, so we can now run the command &lt;code&gt;go&lt;&#x2F;code&gt; to start executing at that address:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;RedBoot&amp;gt; go
&lt;&#x2F;span&gt;&lt;span&gt;ELF-loader image started:   paddr=[0x00100000..0x0017c000]
&lt;&#x2F;span&gt;&lt;span&gt;ELF-loading kernel image:   paddr=[0x01000000..0x01039154] vaddr=[0xf0000000..0xf0039154] v_entry=0xf0000000
&lt;&#x2F;span&gt;&lt;span&gt;ELF-loading userland image: paddr=[0x00407000..0x005d0000] vaddr=[0x00007000..0x001d0000] v_entry=0x000080b4
&lt;&#x2F;span&gt;&lt;span&gt;Enabling MMU and paging
&lt;&#x2F;span&gt;&lt;span&gt;Jumping to kernel-image entry point
&lt;&#x2F;span&gt;&lt;span&gt;Bootstrapping kernel
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;SOS Starting...
&lt;&#x2F;span&gt;&lt;span&gt;Info Page:  0x001d1000
&lt;&#x2F;span&gt;&lt;span&gt;IPC Buffer: 0x001d0000
&lt;&#x2F;span&gt;&lt;span&gt;Node ID: 0 (of 1)
&lt;&#x2F;span&gt;&lt;span&gt;IOPT levels: 0
&lt;&#x2F;span&gt;&lt;span&gt;Init cnode size bits: 12
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;Cap details:
&lt;&#x2F;span&gt;&lt;span&gt;Type              Start      End
&lt;&#x2F;span&gt;&lt;span&gt;Empty             0x00000266 0x00001000
&lt;&#x2F;span&gt;&lt;span&gt;Shared frames     0x00000000 0x00000000
&lt;&#x2F;span&gt;&lt;span&gt;User image frames 0x0000000c 0x000001d5
&lt;&#x2F;span&gt;&lt;span&gt;User image PTs    0x000001d5 0x000001d7
&lt;&#x2F;span&gt;&lt;span&gt;Untypeds          0x000001d7 0x000001f7
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;Untyped details:
&lt;&#x2F;span&gt;&lt;span&gt;Untyped Slot       Paddr      Bits
&lt;&#x2F;span&gt;&lt;span&gt;  0     0x000001d7 0x01000000 12
&lt;&#x2F;span&gt;&lt;span&gt;  1     0x000001d8 0x01001000 12
&lt;&#x2F;span&gt;&lt;span&gt;  2     0x000001d9 0x01002000 12
&lt;&#x2F;span&gt;&lt;span&gt;  3     0x000001da 0x01003000 12
&lt;&#x2F;span&gt;&lt;span&gt;  4     0x000001db 0x01004000 12
&lt;&#x2F;span&gt;&lt;span&gt;  5     0x000001dc 0x01005000 12
&lt;&#x2F;span&gt;&lt;span&gt;  6     0x000001dd 0x01006000 12
&lt;&#x2F;span&gt;&lt;span&gt;  7     0x000001de 0x01007000 12
&lt;&#x2F;span&gt;&lt;span&gt;  8     0x000001df 0x01008000 12
&lt;&#x2F;span&gt;&lt;span&gt;  9     0x000001e0 0x01009000 12
&lt;&#x2F;span&gt;&lt;span&gt; 10     0x000001e1 0x0100a000 12
&lt;&#x2F;span&gt;&lt;span&gt; 11     0x000001e2 0x0100b000 12
&lt;&#x2F;span&gt;&lt;span&gt; 12     0x000001e3 0x0100c000 12
&lt;&#x2F;span&gt;&lt;span&gt; 13     0x000001e4 0x0100d000 12
&lt;&#x2F;span&gt;&lt;span&gt; 14     0x000001e5 0x0100e000 12
&lt;&#x2F;span&gt;&lt;span&gt; 15     0x000001e6 0x0100f000 12
&lt;&#x2F;span&gt;&lt;span&gt; 16     0x000001e7 0x01ff0000 13
&lt;&#x2F;span&gt;&lt;span&gt; 17     0x000001e8 0x01064000 14
&lt;&#x2F;span&gt;&lt;span&gt; 18     0x000001e9 0x01068000 15
&lt;&#x2F;span&gt;&lt;span&gt; 19     0x000001ea 0x01070000 16
&lt;&#x2F;span&gt;&lt;span&gt; 20     0x000001eb 0x01fe0000 16
&lt;&#x2F;span&gt;&lt;span&gt; 21     0x000001ec 0x01fc0000 17
&lt;&#x2F;span&gt;&lt;span&gt; 22     0x000001ed 0x01f80000 18
&lt;&#x2F;span&gt;&lt;span&gt; 23     0x000001ee 0x01080000 19
&lt;&#x2F;span&gt;&lt;span&gt; 24     0x000001ef 0x01f00000 19
&lt;&#x2F;span&gt;&lt;span&gt; 25     0x000001f0 0x01100000 20
&lt;&#x2F;span&gt;&lt;span&gt; 26     0x000001f1 0x01e00000 20
&lt;&#x2F;span&gt;&lt;span&gt; 27     0x000001f2 0x01200000 21
&lt;&#x2F;span&gt;&lt;span&gt; 28     0x000001f3 0x01c00000 21
&lt;&#x2F;span&gt;&lt;span&gt; 29     0x000001f4 0x01400000 22
&lt;&#x2F;span&gt;&lt;span&gt; 30     0x000001f5 0x01800000 22
&lt;&#x2F;span&gt;&lt;span&gt; 31     0x000001f6 0x0103a000 13
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;Num device regions: 13
&lt;&#x2F;span&gt;&lt;span&gt;Device Addr     Size Start      End
&lt;&#x2F;span&gt;&lt;span&gt; 0 0xc8001000 12 0x000001f7 0x000001f8
&lt;&#x2F;span&gt;&lt;span&gt; 1 0xc8002000 12 0x000001f8 0x000001f9
&lt;&#x2F;span&gt;&lt;span&gt; 2 0xc8004000 12 0x000001f9 0x000001fa
&lt;&#x2F;span&gt;&lt;span&gt; 3 0xc8005000 12 0x000001fa 0x000001fb
&lt;&#x2F;span&gt;&lt;span&gt; 4 0xc8006000 12 0x000001fb 0x000001fc
&lt;&#x2F;span&gt;&lt;span&gt; 5 0xc8007000 12 0x000001fc 0x000001fd
&lt;&#x2F;span&gt;&lt;span&gt; 6 0xc8008000 12 0x000001fd 0x000001fe
&lt;&#x2F;span&gt;&lt;span&gt; 7 0xc8009000 12 0x000001fe 0x000001ff
&lt;&#x2F;span&gt;&lt;span&gt; 8 0xc800a000 12 0x000001ff 0x00000200
&lt;&#x2F;span&gt;&lt;span&gt; 9 0xc800b000 12 0x00000200 0x00000201
&lt;&#x2F;span&gt;&lt;span&gt;10 0x50000000 12 0x00000201 0x00000261
&lt;&#x2F;span&gt;&lt;span&gt;11 0x60000000 12 0x00000261 0x00000265
&lt;&#x2F;span&gt;&lt;span&gt;12 0xc4000000 12 0x00000265 0x00000266
&lt;&#x2F;span&gt;&lt;span&gt;-----------------------------------------
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;Parsing Dite data:
&lt;&#x2F;span&gt;&lt;span&gt;Found section              sos at index 0 (0x00007018)
&lt;&#x2F;span&gt;&lt;span&gt;        flags 0
&lt;&#x2F;span&gt;&lt;span&gt;        entry 80b4
&lt;&#x2F;span&gt;&lt;span&gt;        base 0x00008000
&lt;&#x2F;span&gt;&lt;span&gt;        size 1838928
&lt;&#x2F;span&gt;&lt;span&gt;        magic (nil)
&lt;&#x2F;span&gt;&lt;span&gt;        pbase 0x00008000
&lt;&#x2F;span&gt;&lt;span&gt;Found section         tty_test at index 1 (0x00007040)
&lt;&#x2F;span&gt;&lt;span&gt;        flags 0
&lt;&#x2F;span&gt;&lt;span&gt;        entry 0
&lt;&#x2F;span&gt;&lt;span&gt;        base 0x001c9000
&lt;&#x2F;span&gt;&lt;span&gt;        size 27863
&lt;&#x2F;span&gt;&lt;span&gt;        magic (nil)
&lt;&#x2F;span&gt;&lt;span&gt;        pbase 0x001c9000
&lt;&#x2F;span&gt;&lt;span&gt;DMA memory is at 0x10000000 to 0x10400000
&lt;&#x2F;span&gt;&lt;span&gt;Initialising the frame table!
&lt;&#x2F;span&gt;&lt;span&gt;Made a frame, physical address 181d000, cap 1646
&lt;&#x2F;span&gt;&lt;span&gt;Made a frame, physical address 181e000, cap 1647
&lt;&#x2F;span&gt;&lt;span&gt;Made a frame, physical address 181f000, cap 1648
&lt;&#x2F;span&gt;&lt;span&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;Made a frame, physical address 18e2000, cap 1843
&lt;&#x2F;span&gt;&lt;span&gt;Made a frame, physical address 18e3000, cap 1844
&lt;&#x2F;span&gt;&lt;span&gt;Made a frame, physical address 18e4000, cap 1845
&lt;&#x2F;span&gt;&lt;span&gt;Initialising the pager!
&lt;&#x2F;span&gt;&lt;span&gt;Leaking a frame.
&lt;&#x2F;span&gt;&lt;span&gt;Mapping our leaked frame to the MASTER_PAGEDIR address (leaking a hardware page table, unavoidable)
&lt;&#x2F;span&gt;&lt;span&gt;Leaking a frame.
&lt;&#x2F;span&gt;&lt;span&gt;Mapping our leaked frame to the MASTER_PAGETABLE address (probably not but possibly also leaking a hardware page table)
&lt;&#x2F;span&gt;&lt;span&gt;Creating the original 4 capdirs
&lt;&#x2F;span&gt;&lt;span&gt;We are mapping in a kernel page at vaddr e3ffa000 to pd 3
&lt;&#x2F;span&gt;&lt;span&gt;We are mapping in a kernel page at vaddr e4000000 to pd 3
&lt;&#x2F;span&gt;&lt;span&gt;Trying to map in a hardware page table at vaddr e4000000 for pd 3
&lt;&#x2F;span&gt;&lt;span&gt;We are mapping in a kernel page at vaddr e3ffb000 to pd 3
&lt;&#x2F;span&gt;&lt;span&gt;We are mapping in a kernel page at vaddr e3ffc000 to pd 3
&lt;&#x2F;span&gt;&lt;span&gt;We are mapping in a kernel page at vaddr e3ffd000 to pd 3
&lt;&#x2F;span&gt;&lt;span&gt;Adding to capdir (cap dir 0 is 0xe3ffa000)
&lt;&#x2F;span&gt;&lt;span&gt;Pager initialised!  Moving morecore across to pager-based allocation!
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;Starting network_init
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;        Network Mask: 225.225.225.0
&lt;&#x2F;span&gt;&lt;span&gt;  Gateway IP Address: 192.168.168.1
&lt;&#x2F;span&gt;&lt;span&gt;    Local IP Address: 192.168.168.2
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;Device memory at 0xb8005000 to 0xb8006000
&lt;&#x2F;span&gt;&lt;span&gt;Device memory at 0x40000000 to 0x40060000
&lt;&#x2F;span&gt;&lt;span&gt;Device memory at 0x50000000 to 0x50004000
&lt;&#x2F;span&gt;&lt;span&gt;Device memory at 0xb8006000 to 0xb8007000
&lt;&#x2F;span&gt;&lt;span&gt;Device memory at 0xb8007000 to 0xb8008000
&lt;&#x2F;span&gt;&lt;span&gt;Device memory at 0xb8008000 to 0xb8009000
&lt;&#x2F;span&gt;&lt;span&gt;Undelivered IRQ: 2
&lt;&#x2F;span&gt;&lt;span&gt;Device memory at 0xb4000000 to 0xb4001000
&lt;&#x2F;span&gt;&lt;span&gt;Device memory at 0xb8009000 to 0xb800a000
&lt;&#x2F;span&gt;&lt;span&gt;Device memory at 0xb800a000 to 0xb800b000
&lt;&#x2F;span&gt;&lt;span&gt;waiting for PHY 0 to become ready...
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;Network failed to respond to ARP query
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;seL4 root server abortedDebug halt syscall from user thread 0xf0063e00
&lt;&#x2F;span&gt;&lt;span&gt;halting...
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This is debug output from our OS project which is pretty exciting. It’s running!
It’s failing an assertion after unsuccessfully attempting to
connect to the network. It has the same IP addresses hard-coded
into it as the initial boot script, so can’t connect to my home network, and the code
is written in such a way that doesn’t let it proceed to boot without network access.&lt;&#x2F;p&gt;
&lt;p&gt;We’ll get to fixing this, but first let’s talk about the goal of this endeavour.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;call-me-maybe&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#call-me-maybe&quot; aria-label=&quot;Anchor link for: call-me-maybe&quot;&gt;Call Me Maybe&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;I built an &lt;a href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;Easter_egg_(media)&quot;&gt;Easter egg&lt;&#x2F;a&gt; into
the OS. The slug has a buzzer, and there’s a particular bit in a hardware register that
can be toggled at a given frequency to buzz at that frequency. I wrote this
code in mid 2012 and so the obvious song to play according to 20 year old me
was &lt;a href=&quot;https:&#x2F;&#x2F;www.youtube.com&#x2F;watch?v=fWNaR-rxAic&quot;&gt;Call Me Maybe by Carly Rae Jepsen&lt;&#x2F;a&gt;. I remember implementing a device driver
with a unix-style “everything’s a file” interface, where opening the file
&lt;code&gt;&#x2F;dev&#x2F;maybe&lt;&#x2F;code&gt; would lock up the OS while it buzzed the hottest song of 2012.&lt;&#x2F;p&gt;
&lt;p&gt;Unfortunately it appears the snapshot of the project I stored on the university
server was not its final form. I did most of the work for this project on a
netbook (remember netbooks?!) which has sadly been lost to the ages. The
implementation of &lt;code&gt;&#x2F;dev&#x2F;maybe&lt;&#x2F;code&gt; is not in this version, but it still has the
logic to play the song and print the lyrics to the terminal:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;c&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-c &quot;&gt;&lt;code class=&quot;language-c&quot; data-lang=&quot;c&quot;&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;void &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;call_me_maybe&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;int &lt;&#x2F;span&gt;&lt;span&gt;beat_len) {
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;printf&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;Hey I just met you&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;\n&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;);
&lt;&#x2F;span&gt;&lt;span&gt;    udelay(beat_len);
&lt;&#x2F;span&gt;&lt;span&gt;    tone(NOTE_G4, beat_len);
&lt;&#x2F;span&gt;&lt;span&gt;    tone(NOTE_B3, beat_len&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;4&lt;&#x2F;span&gt;&lt;span&gt;);
&lt;&#x2F;span&gt;&lt;span&gt;    tone(NOTE_D4, beat_len&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;2&lt;&#x2F;span&gt;&lt;span&gt;);
&lt;&#x2F;span&gt;&lt;span&gt;    tone(NOTE_G4, beat_len&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;4&lt;&#x2F;span&gt;&lt;span&gt;);
&lt;&#x2F;span&gt;&lt;span&gt;    tone(NOTE_G4, beat_len&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;4&lt;&#x2F;span&gt;&lt;span&gt;);
&lt;&#x2F;span&gt;&lt;span&gt;    tone(NOTE_D4, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;3&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;*&lt;&#x2F;span&gt;&lt;span&gt;beat_len&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;4&lt;&#x2F;span&gt;&lt;span&gt;);
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;printf&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;And this is crazy&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;\n&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;);
&lt;&#x2F;span&gt;&lt;span&gt;    udelay(beat_len);
&lt;&#x2F;span&gt;&lt;span&gt;    udelay(beat_len&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;2&lt;&#x2F;span&gt;&lt;span&gt;);
&lt;&#x2F;span&gt;&lt;span&gt;    tone(NOTE_B3, beat_len&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;2&lt;&#x2F;span&gt;&lt;span&gt;);
&lt;&#x2F;span&gt;&lt;span&gt;    tone(NOTE_B3, beat_len&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;4&lt;&#x2F;span&gt;&lt;span&gt;);
&lt;&#x2F;span&gt;&lt;span&gt;    tone(NOTE_D4, beat_len&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;2&lt;&#x2F;span&gt;&lt;span&gt;);
&lt;&#x2F;span&gt;&lt;span&gt;    tone(NOTE_B4, beat_len&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;4&lt;&#x2F;span&gt;&lt;span&gt;);
&lt;&#x2F;span&gt;&lt;span&gt;    tone(NOTE_B4, beat_len&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;2&lt;&#x2F;span&gt;&lt;span&gt;);
&lt;&#x2F;span&gt;&lt;span&gt;    tone(NOTE_G4, beat_len&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;2&lt;&#x2F;span&gt;&lt;span&gt;);
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;printf&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;But here&amp;#39;s my number&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;\n&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;);
&lt;&#x2F;span&gt;&lt;span&gt;    udelay(beat_len);
&lt;&#x2F;span&gt;&lt;span&gt;    udelay(beat_len&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;2&lt;&#x2F;span&gt;&lt;span&gt;);
&lt;&#x2F;span&gt;&lt;span&gt;    tone(NOTE_G4, beat_len&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;2&lt;&#x2F;span&gt;&lt;span&gt;);
&lt;&#x2F;span&gt;&lt;span&gt;    tone(NOTE_B4, beat_len&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;2&lt;&#x2F;span&gt;&lt;span&gt;);
&lt;&#x2F;span&gt;&lt;span&gt;    tone(NOTE_C5, beat_len&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;2&lt;&#x2F;span&gt;&lt;span&gt;);
&lt;&#x2F;span&gt;&lt;span&gt;    tone(NOTE_B4, beat_len&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;2&lt;&#x2F;span&gt;&lt;span&gt;);
&lt;&#x2F;span&gt;&lt;span&gt;    tone(NOTE_G4, beat_len&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;2&lt;&#x2F;span&gt;&lt;span&gt;);
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;printf&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;So call me maybe&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;\n&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;);
&lt;&#x2F;span&gt;&lt;span&gt;    udelay(beat_len);
&lt;&#x2F;span&gt;&lt;span&gt;    udelay(beat_len&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;2&lt;&#x2F;span&gt;&lt;span&gt;);
&lt;&#x2F;span&gt;&lt;span&gt;    tone(NOTE_G4, beat_len&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;2&lt;&#x2F;span&gt;&lt;span&gt;);
&lt;&#x2F;span&gt;&lt;span&gt;    tone(NOTE_B4, beat_len&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;2&lt;&#x2F;span&gt;&lt;span&gt;);
&lt;&#x2F;span&gt;&lt;span&gt;    tone(NOTE_A4, beat_len&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;2&lt;&#x2F;span&gt;&lt;span&gt;);
&lt;&#x2F;span&gt;&lt;span&gt;    tone(NOTE_A4, beat_len&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;2&lt;&#x2F;span&gt;&lt;span&gt;);
&lt;&#x2F;span&gt;&lt;span&gt;    tone(NOTE_G4, beat_len&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;2&lt;&#x2F;span&gt;&lt;span&gt;);
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Rather than playing the song by reading a file, this version of the project
would play it immediately after booting if a certain compile-time flag was set:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;    if (I_JUST_MET_YOU) {
&lt;&#x2F;span&gt;&lt;span&gt;        call_me_maybe(500000);
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;However the flag was not set:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;c&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-c &quot;&gt;&lt;code class=&quot;language-c&quot; data-lang=&quot;c&quot;&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;#define &lt;&#x2F;span&gt;&lt;span&gt;I_JUST_MET_YOU &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;0
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The C compiler was probably smart enough to elide the entire call to
&lt;code&gt;call_me_maybe&lt;&#x2F;code&gt; since that condition is never true. Fortunately the presence of
the song’s lyrics in the bootable image tells us that the &lt;code&gt;call_me_maybe&lt;&#x2F;code&gt; function did
make it into the image despite never being called:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;$ strings bootimg.bin | grep Hey
&lt;&#x2F;span&gt;&lt;span&gt;Hey I just met you
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;So once the network issue is fixed, my goal is going to be getting Call Me Maybe
to play on the buzzer of my slug.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;em&gt;Why don’t you just call the &lt;code&gt;call_me_maybe&lt;&#x2F;code&gt; function?&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Well…&lt;&#x2F;p&gt;
&lt;h2 id=&quot;fixing-the-network&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#fixing-the-network&quot; aria-label=&quot;Anchor link for: fixing-the-network&quot;&gt;Fixing the Network&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;I can’t build the project. I have the source code, and luckily I have a mostly
working bootable image. I also have the object files for each source file which
were built with debug symbols. But I don’t have the software necessary to
change the source code and rebuild the image to incorporate those changes.&lt;&#x2F;p&gt;
&lt;p&gt;And I refuse to do the kind of archaeology that would be required to get a
32-bit ARM compiler toolchain and seL4 build scripts circa 2012 running on a modern OS.
Getting all the tools working was hard enough back in 2012.&lt;&#x2F;p&gt;
&lt;p&gt;In order to get past the failing assertion we need the OS to choose an IP
address in a valid range for my home network. Currently it tries to use
192.168.168.2:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;        Network Mask: 225.225.225.0
&lt;&#x2F;span&gt;&lt;span&gt;  Gateway IP Address: 192.168.168.1
&lt;&#x2F;span&gt;&lt;span&gt;    Local IP Address: 192.168.168.2
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The network information is a compile-time constant string which means these
strings are literally present in the binary:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;$ strings bootimg.bin | grep &amp;#39;192\.168&amp;#39;
&lt;&#x2F;span&gt;&lt;span&gt;192.168.168.1
&lt;&#x2F;span&gt;&lt;span&gt;192.168.168.2
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;To change them to suitable addresses, one can simply &lt;em&gt;modify the binary
directly&lt;&#x2F;em&gt;! I don’t own a &lt;a href=&quot;https:&#x2F;&#x2F;xkcd.com&#x2F;378&#x2F;&quot;&gt;magnetized needle&lt;&#x2F;a&gt; so instead
I’ll use the tool &lt;code&gt;hexedit&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;The &lt;code&gt;hexedit&lt;&#x2F;code&gt; tool lets you print and modify the raw bytes in any type of file.
When you run &lt;code&gt;hexedit bootimg.bin&lt;&#x2F;code&gt; it prints the hexadecimal and ASCII
representations of the binary data in the file:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;000000B0   E5 9F 03 24  E5 9F 13 24  E5 9F 23 24  EB 00 04 97  ...$...$..#$....
&lt;&#x2F;span&gt;&lt;span&gt;000000C0   E5 9F 03 20  EB 00 00 F6  E3 50 00 00  0A 00 00 02  ... .....P......
&lt;&#x2F;span&gt;&lt;span&gt;000000D0   E5 9F 03 14  EB 00 04 9F  EB 00 00 D1  E5 9F 43 04  ..............C.
&lt;&#x2F;span&gt;&lt;span&gt;000000E0   E1 A0 00 04  E3 A0 10 01  E2 8D 20 40  E2 8D 30 38  .......... @..08
&lt;&#x2F;span&gt;&lt;span&gt;000000F0   EB 00 01 F3  E1 A0 00 04  E3 A0 10 00  E2 8D 20 20  ..............
&lt;&#x2F;span&gt;&lt;span&gt;00000100   E2 8D 30 18  EB 00 01 EE  E1 A0 00 04  EB 00 01 DB  ..0.............
&lt;&#x2F;span&gt;&lt;span&gt;00000110   E1 A0 90 01  E5 9D 30 1C  E5 8D 30 00  E5 8D 10 04  ......0...0.....
&lt;&#x2F;span&gt;&lt;span&gt;00000120   E5 9F 02 C8  E5 9D 10 44  E5 9D 20 3C  E5 9D 30 24  .......D.. &amp;lt;..0$
&lt;&#x2F;span&gt;&lt;span&gt;00000130   EB 00 04 7A  E5 9F 62 A4  E5 9F A2 A4  E5 9D 00 44  ...z..b........D
&lt;&#x2F;span&gt;&lt;span&gt;00000140   E5 9D 10 3C  E2 41 10 01  E1 A0 20 06  E1 A0 30 0A  ...&amp;lt;.A.... ...0.
&lt;&#x2F;span&gt;&lt;span&gt;00000150   EB FF FF CC  E3 50 00 00  0A 00 00 02  E5 9F 02 90  .....P..........
&lt;&#x2F;span&gt;&lt;span&gt;00000160   EB 00 04 7C  EB 00 00 AE  E5 9F 02 78  E3 A0 10 01  ...|.......x....
&lt;&#x2F;span&gt;&lt;span&gt;00000170   EB 00 02 35  E3 50 00 00  1A 00 00 02  E5 9F 02 74  ...5.P.........t
&lt;&#x2F;span&gt;&lt;span&gt;00000180   EB 00 04 74  EB 00 00 A6  E5 9F 02 6C  EB 00 00 C4  ...t.......l....
&lt;&#x2F;span&gt;&lt;span&gt;00000190   E3 50 00 00  0A 00 00 02  E5 9F 02 60  EB 00 04 6D  .P.........`...m
&lt;&#x2F;span&gt;&lt;span&gt;000001A0   EB 00 00 9F  E5 9F 42 50  E1 A0 00 04  E3 A0 10 01  ......BP........
&lt;&#x2F;span&gt;&lt;span&gt;-**  bootimg.bin       --0x0&#x2F;0x7C000--0%---------------------------------------
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;To find the IP addresses, we’ll be changing, search for a sequence of hexadecimal digits representing
the ASCII encoding of part of the address.&lt;&#x2F;p&gt;
&lt;p&gt;Here’s an ASCII table so you can play along at home:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;Dec Hex    Dec Hex    Dec Hex  Dec Hex  Dec Hex  Dec Hex   Dec Hex   Dec Hex
&lt;&#x2F;span&gt;&lt;span&gt;  0 00 NUL  16 10 DLE  32 20    48 30 0  64 40 @  80 50 P   96 60 `  112 70 p
&lt;&#x2F;span&gt;&lt;span&gt;  1 01 SOH  17 11 DC1  33 21 !  49 31 1  65 41 A  81 51 Q   97 61 a  113 71 q
&lt;&#x2F;span&gt;&lt;span&gt;  2 02 STX  18 12 DC2  34 22 &amp;quot;  50 32 2  66 42 B  82 52 R   98 62 b  114 72 r
&lt;&#x2F;span&gt;&lt;span&gt;  3 03 ETX  19 13 DC3  35 23 #  51 33 3  67 43 C  83 53 S   99 63 c  115 73 s
&lt;&#x2F;span&gt;&lt;span&gt;  4 04 EOT  20 14 DC4  36 24 $  52 34 4  68 44 D  84 54 T  100 64 d  116 74 t
&lt;&#x2F;span&gt;&lt;span&gt;  5 05 ENQ  21 15 NAK  37 25 %  53 35 5  69 45 E  85 55 U  101 65 e  117 75 u
&lt;&#x2F;span&gt;&lt;span&gt;  6 06 ACK  22 16 SYN  38 26 &amp;amp;  54 36 6  70 46 F  86 56 V  102 66 f  118 76 v
&lt;&#x2F;span&gt;&lt;span&gt;  7 07 BEL  23 17 ETB  39 27 &amp;#39;  55 37 7  71 47 G  87 57 W  103 67 g  119 77 w
&lt;&#x2F;span&gt;&lt;span&gt;  8 08 BS   24 18 CAN  40 28 (  56 38 8  72 48 H  88 58 X  104 68 h  120 78 x
&lt;&#x2F;span&gt;&lt;span&gt;  9 09 HT   25 19 EM   41 29 )  57 39 9  73 49 I  89 59 Y  105 69 i  121 79 y
&lt;&#x2F;span&gt;&lt;span&gt; 10 0A LF   26 1A SUB  42 2A *  58 3A :  74 4A J  90 5A Z  106 6A j  122 7A z
&lt;&#x2F;span&gt;&lt;span&gt; 11 0B VT   27 1B ESC  43 2B +  59 3B ;  75 4B K  91 5B [  107 6B k  123 7B {
&lt;&#x2F;span&gt;&lt;span&gt; 12 0C FF   28 1C FS   44 2C ,  60 3C &amp;lt;  76 4C L  92 5C \  108 6C l  124 7C |
&lt;&#x2F;span&gt;&lt;span&gt; 13 0D CR   29 1D GS   45 2D -  61 3D =  77 4D M  93 5D ]  109 6D m  125 7D }
&lt;&#x2F;span&gt;&lt;span&gt; 14 0E SO   30 1E RS   46 2E .  62 3E &amp;gt;  78 4E N  94 5E ^  110 6E n  126 7E ~
&lt;&#x2F;span&gt;&lt;span&gt; 15 0F SI   31 1F US   47 2F &#x2F;  63 3F ?  79 4F O  95 5F _  111 6F o  127 7F DEL
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;For example to find &lt;code&gt;192&lt;&#x2F;code&gt;, search for &lt;code&gt;0x313932&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;0005F5E0   30 00 00 00  4E 65 74 77  6F 72 6B 20  4D 61 73 6B  0...Network Mask
&lt;&#x2F;span&gt;&lt;span&gt;0005F5F0   00 00 00 00  &lt;span style=&quot;background-color:red&quot;&gt;31 39 32 2E  31 36 38 2E  31 36 38 2E&lt;&#x2F;span&gt;  ....&lt;span style=&quot;background-color:red&quot;&gt;192.168.168.&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;0005F600   &lt;span style=&quot;background-color:red&quot;&gt;31&lt;&#x2F;span&gt; 00 00 00  47 61 74 65  77 61 79 20  49 50 20 41  &lt;span style=&quot;background-color:red&quot;&gt;1&lt;&#x2F;span&gt;...Gateway IP A
&lt;&#x2F;span&gt;&lt;span&gt;0005F610   64 64 72 65  73 73 00 00  00 00 00 00  &lt;span style=&quot;background-color:green&quot;&gt;31 39 32 2E&lt;&#x2F;span&gt;  ddress......&lt;span style=&quot;background-color:green&quot;&gt;192.&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;0005F620   &lt;span style=&quot;background-color:green&quot;&gt;31 36 38 2E  31 36 38 2E  32&lt;&#x2F;span&gt; 00 00 00  4C 6F 63 61  &lt;span style=&quot;background-color:green&quot;&gt;168.168.2&lt;&#x2F;span&gt;...Loca
&lt;&#x2F;span&gt;&lt;span&gt;0005F630   6C 20 49 50  20 41 64 64  72 65 73 73  00 00 00 00  l IP Address....
&lt;&#x2F;span&gt;&lt;span&gt;0005F640   00 00 00 00  6E 65 74 69  66 20 21 3D  20 4E 55 4C  ....netif != NUL
&lt;&#x2F;span&gt;&lt;span&gt;---  bootimg.bin       --0x5F5E4&#x2F;0x7C000--77%----------------------------------
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The ASCII representation of the data confirms that we’re in the right place.&lt;&#x2F;p&gt;
&lt;p&gt;The game is that all the changes to the binary file must be made by updating values in-place.
It’s not possible to insert new bytes between existing bytes, nor to remove bytes.
This is because parts of the code will refer to other parts of the code and
static data (like these IP addresses) by their relative offset from one
another. If we insert or remove bytes then that will change the relative
position of all following bytes, causing references to no longer point to the
correct place.&lt;&#x2F;p&gt;
&lt;p&gt;I changed the local and gateway addresses to &lt;code&gt;192.168.1.25&lt;&#x2F;code&gt; and &lt;code&gt;192.168.1.1&lt;&#x2F;code&gt;
respectively, again choosing an arbitrary unused valid address for the local
address and my router’s address as the gateway address. The new addresses are
made up of fewer characters than the original addresses so they’ll fit in the
allocated space. I overwrote the remaining characters of the original strings
with 0s as these as null-terminated C strings (so technically I just needed to
put a single 0 byte immediately after the end of each string).&lt;&#x2F;p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;0005F5E0   30 00 00 00  4E 65 74 77  6F 72 6B 20  4D 61 73 6B  0...Network Mask
&lt;&#x2F;span&gt;&lt;span&gt;0005F5F0   00 00 00 00  &lt;span style=&quot;background-color:red&quot;&gt;31 39 32 2E  31 36 38 2E  31 2E 31&lt;&#x2F;span&gt; 00  ....&lt;span style=&quot;background-color:red&quot;&gt;192.168.1.1&lt;&#x2F;span&gt;.
&lt;&#x2F;span&gt;&lt;span&gt;0005F600   00 00 00 00  47 61 74 65  77 61 79 20  49 50 20 41  ....Gateway IP A
&lt;&#x2F;span&gt;&lt;span&gt;0005F610   64 64 72 65  73 73 00 00  00 00 00 00  &lt;span style=&quot;background-color:green&quot;&gt;31 39 32 2E&lt;&#x2F;span&gt;  ddress......&lt;span style=&quot;background-color:green&quot;&gt;192.&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;0005F620   &lt;span style=&quot;background-color:green&quot;&gt;31 36 38 2E  31 2E 32 35&lt;&#x2F;span&gt;  00 00 00 00  4C 6F 63 61  &lt;span style=&quot;background-color:green&quot;&gt;168.1.25&lt;&#x2F;span&gt;....Loca
&lt;&#x2F;span&gt;&lt;span&gt;0005F630   6C 20 49 50  20 41 64 64  72 65 73 73  00 00 00 00  l IP Address....
&lt;&#x2F;span&gt;&lt;span&gt;0005F640   00 00 00 00  6E 65 74 69  66 20 21 3D  20 4E 55 4C  ....netif != NUL
&lt;&#x2F;span&gt;&lt;span&gt;-**  bootimg.bin       --0x5F629&#x2F;0x7C000--77%----------------------------------
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;After re-&lt;code&gt;gzip&lt;&#x2F;code&gt;-ing the image and resetting the slug, and rerunning all the
RedBoot commands as before, the OS makes it past the network initialization
successfully:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;Starting network_init
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;        Network Mask: 225.225.225.0
&lt;&#x2F;span&gt;&lt;span&gt;  Gateway IP Address: 192.168.1.1
&lt;&#x2F;span&gt;&lt;span&gt;    Local IP Address: 192.168.1.25
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;Device memory at 0xb8005000 to 0xb8006000
&lt;&#x2F;span&gt;&lt;span&gt;Device memory at 0x40000000 to 0x40060000
&lt;&#x2F;span&gt;&lt;span&gt;Device memory at 0x50000000 to 0x50004000
&lt;&#x2F;span&gt;&lt;span&gt;Device memory at 0xb8006000 to 0xb8007000
&lt;&#x2F;span&gt;&lt;span&gt;Device memory at 0xb8007000 to 0xb8008000
&lt;&#x2F;span&gt;&lt;span&gt;Device memory at 0xb8008000 to 0xb8009000
&lt;&#x2F;span&gt;&lt;span&gt;Undelivered IRQ: 2
&lt;&#x2F;span&gt;&lt;span&gt;Device memory at 0xb4000000 to 0xb4001000
&lt;&#x2F;span&gt;&lt;span&gt;Device memory at 0xb8009000 to 0xb800a000
&lt;&#x2F;span&gt;&lt;span&gt;Device memory at 0xb800a000 to 0xb800b000
&lt;&#x2F;span&gt;&lt;span&gt;waiting for PHY 0 to become ready....
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;Mounting NFS
&lt;&#x2F;span&gt;&lt;span&gt;Error receiving time using UDP time protocol
&lt;&#x2F;span&gt;&lt;span&gt;Failed to initialise NFS
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;Setting up capabilities for timer...
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;Starting timer...
&lt;&#x2F;span&gt;&lt;span&gt;Undelivered IRQ: 5
&lt;&#x2F;span&gt;&lt;span&gt;[timer] starting timer!
&lt;&#x2F;span&gt;&lt;span&gt;[timer] base address: c8005000
&lt;&#x2F;span&gt;&lt;span&gt;[timer] frame count: 1
&lt;&#x2F;span&gt;&lt;span&gt;[timer] frame size: 4096
&lt;&#x2F;span&gt;&lt;span&gt;[timer] paging success
&lt;&#x2F;span&gt;&lt;span&gt;Getting timestamp...
&lt;&#x2F;span&gt;&lt;span&gt;[gpio] starting
&lt;&#x2F;span&gt;&lt;span&gt;[gpio] found region 0x001d13a8
&lt;&#x2F;span&gt;&lt;span&gt;[gpio] paging success
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;Starting &amp;quot;tty_test&amp;quot;...
&lt;&#x2F;span&gt;&lt;span&gt;Loading first process.  Trying to set up a page table!
&lt;&#x2F;span&gt;&lt;span&gt;init&amp;#39;d some startup stuff! - only_process_page_dir is at 0x000b9cb0, only_procss_vroot (sel4_pd) is 1985
&lt;&#x2F;span&gt;&lt;span&gt; * Loading segment 00008000--&amp;gt;0000a3c0
&lt;&#x2F;span&gt;&lt;span&gt;We are mapping in a kernel page at vaddr 8000 to pd 1985
&lt;&#x2F;span&gt;&lt;span&gt;Trying to map in a hardware page table at vaddr 0 for pd 1985
&lt;&#x2F;span&gt;&lt;span&gt;We are mapping in a kernel page at vaddr d0008000 to pd 3
&lt;&#x2F;span&gt;&lt;span&gt;Trying to map in a hardware page table at vaddr d0000000 for pd 3
&lt;&#x2F;span&gt;&lt;span&gt;We are mapping in a kernel page at vaddr e4001000 to pd 3
&lt;&#x2F;span&gt;&lt;span&gt;We are mapping in a kernel page at vaddr 9000 to pd 1985
&lt;&#x2F;span&gt;&lt;span&gt;We are mapping in a kernel page at vaddr d0009000 to pd 3
&lt;&#x2F;span&gt;&lt;span&gt;We are mapping in a kernel page at vaddr a000 to pd 1985
&lt;&#x2F;span&gt;&lt;span&gt;We are mapping in a kernel page at vaddr d000a000 to pd 3
&lt;&#x2F;span&gt;&lt;span&gt; * Loading segment 000123c0--&amp;gt;00013490
&lt;&#x2F;span&gt;&lt;span&gt;We are mapping in a kernel page at vaddr 12000 to pd 1985
&lt;&#x2F;span&gt;&lt;span&gt;We are mapping in a kernel page at vaddr d0012000 to pd 3
&lt;&#x2F;span&gt;&lt;span&gt;We are mapping in a kernel page at vaddr 13000 to pd 1985
&lt;&#x2F;span&gt;&lt;span&gt;We are mapping in a kernel page at vaddr d0013000 to pd 3
&lt;&#x2F;span&gt;&lt;span&gt;Mapping in a hardware page (vaddr 8ffff000, pd 1985)
&lt;&#x2F;span&gt;&lt;span&gt;We are mapping in a kernel page at vaddr 8ffff000 to pd 1985
&lt;&#x2F;span&gt;&lt;span&gt;Trying to map in a hardware page table at vaddr 8ff00000 for pd 1985
&lt;&#x2F;span&gt;&lt;span&gt;Paged in the stack frame.
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;SOS entering syscall loop
&lt;&#x2F;span&gt;&lt;span&gt;vm fault at 0x8ffdffd0, pc = 0x000083a0, Data fault
&lt;&#x2F;span&gt;&lt;span&gt;Mapping in a hardware page (vaddr 8ffdffd0, pd 1985)
&lt;&#x2F;span&gt;&lt;span&gt;We are mapping in a kernel page at vaddr 8ffdf000 to pd 1985
&lt;&#x2F;span&gt;&lt;span&gt;vm fault at 0x8ffe0fe8, pc = 0x000083ac, Data fault
&lt;&#x2F;span&gt;&lt;span&gt;Mapping in a hardware page (vaddr 8ffe0fe8, pd 1985)
&lt;&#x2F;span&gt;&lt;span&gt;We are mapping in a kernel page at vaddr 8ffe0000 to pd 1985
&lt;&#x2F;span&gt;&lt;span&gt;vm fault at 0x8ffe1fe8, pc = 0x000083ac, Data fault
&lt;&#x2F;span&gt;&lt;span&gt;Mapping in a hardware page (vaddr 8ffe1fe8, pd 1985)
&lt;&#x2F;span&gt;&lt;span&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;We are mapping in a kernel page at vaddr 8fffc000 to pd 1985
&lt;&#x2F;span&gt;&lt;span&gt;vm fault at 0x8fffdfe8, pc = 0x000083ac, Data fault
&lt;&#x2F;span&gt;&lt;span&gt;Mapping in a hardware page (vaddr 8fffdfe8, pd 1985)
&lt;&#x2F;span&gt;&lt;span&gt;We are mapping in a kernel page at vaddr 8fffd000 to pd 1985
&lt;&#x2F;span&gt;&lt;span&gt;vm fault at 0x8fffefe8, pc = 0x000083ac, Data fault
&lt;&#x2F;span&gt;&lt;span&gt;Mapping in a hardware page (vaddr 8fffefe8, pd 1985)
&lt;&#x2F;span&gt;&lt;span&gt;We are mapping in a kernel page at vaddr 8fffe000 to pd 1985
&lt;&#x2F;span&gt;&lt;span&gt;brk syscall, tty_brk = 32498
&lt;&#x2F;span&gt;&lt;span&gt;vm fault at 0x0003249c, pc = 0x000096a4, Data fault
&lt;&#x2F;span&gt;&lt;span&gt;PANIC &#x2F;home&#x2F;steve&#x2F;src&#x2F;aos&#x2F;aoshg&#x2F;apps&#x2F;sos&#x2F;src&#x2F;main.c-handle_page_fault:161 segmentation fault
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;Debug halt syscall from user thread 0xf0063e00
&lt;&#x2F;span&gt;&lt;span&gt;halting...
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;It still crashes eventually but that’s ok. Remember this is a snapshot of the project when
it was half finished. What matters is that it prints the line:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;SOS entering syscall loop
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Looking at the &lt;code&gt;main&lt;&#x2F;code&gt; function:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;c&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-c &quot;&gt;&lt;code class=&quot;language-c&quot; data-lang=&quot;c&quot;&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;    gpio_init(_boot_info);
&lt;&#x2F;span&gt;&lt;span&gt;    buzzer_init();
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;if&lt;&#x2F;span&gt;&lt;span&gt;(I_JUST_MET_YOU) {
&lt;&#x2F;span&gt;&lt;span&gt;        call_me_maybe(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;500000&lt;&#x2F;span&gt;&lt;span&gt;);
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;* Start the user application *&#x2F;
&lt;&#x2F;span&gt;&lt;span&gt;    start_first_process(TTY_NAME, _sos_ipc_ep_cap);
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;* Initialise the serial connection for tty_test *&#x2F;
&lt;&#x2F;span&gt;&lt;span&gt;    console_init();
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;* Wait on synchronous endpoint for IPC *&#x2F;
&lt;&#x2F;span&gt;&lt;span&gt;    dprintf(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;\n&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;SOS entering syscall loop&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;\n&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;);
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The printout happens after the buzzer has been initialized. This means that
all the necessary hardware initialization has taken place to play the song.
So now we just need to call the &lt;code&gt;call_me_maybe&lt;&#x2F;code&gt; function.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;just-calling-the-call-me-maybe-function&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#just-calling-the-call-me-maybe-function&quot; aria-label=&quot;Anchor link for: just-calling-the-call-me-maybe-function&quot;&gt;Just calling the &lt;code&gt;call_me_maybe&lt;&#x2F;code&gt; function&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;To call a function we need to know its address. Specifically its &lt;em&gt;virtual&lt;&#x2F;em&gt;
address. This is determined at runtime by how memory is mapped by the page
table. The logic for setting up the page table up exists somewhere in the project, but trying
to determine which virtual address maps to a particular offset into the bootable
image without the ability to instrument the code or any debugging tooling sounds
unpleasant.&lt;&#x2F;p&gt;
&lt;p&gt;No matter what approach I eventually take, I want to know where in the
binary image &lt;code&gt;call_me_maybe&lt;&#x2F;code&gt; is located. To do this we’ll benefit from some
slightly more sophisticated tools. It will be helpful to be able to use the
debug symbols in object files to learn which sequences of instructions in the
binary correspond to which functions in the source code. Short of setting up an
ARM toolchain, I found that the &lt;code&gt;objdump&lt;&#x2F;code&gt; implementation that comes with LLVM
(specifically the &lt;code&gt;llvm&lt;&#x2F;code&gt; derivation from nixpkgs) can disassemble the object
files in the project well enough, despite them having been compiled for a
foreign architecture (I’m using the x86_64 llvm package and these files were
compiled for 32-bit ARM).&lt;&#x2F;p&gt;
&lt;p&gt;Since &lt;code&gt;call_me_maybe&lt;&#x2F;code&gt; is defined in &lt;code&gt;buzzer.c&lt;&#x2F;code&gt;, I ran &lt;code&gt;llvm-objdump -d build&#x2F;arm&#x2F;nslu2&#x2F;sos&#x2F;src&#x2F;buzzer.o&lt;&#x2F;code&gt;. The relevant part of the output is:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;00000054 &amp;lt;buzzer_init&amp;gt;:
&lt;&#x2F;span&gt;&lt;span&gt;      54: e3 a0 23 3a   blo     #9339788 &amp;lt;$d+0x8e815c&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;      58: e5 9f 30 04   ldrteq  r9, [r0], #-4069
&lt;&#x2F;span&gt;&lt;span&gt;      5c: e5 83 20 00   eoreq   r8, r0, r5, ror #7
&lt;&#x2F;span&gt;&lt;span&gt;      60: e1 2f ff 1e   cdpne   p15, #15, c2, c15, c1, #7
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;00000064 &amp;lt;$d&amp;gt;:
&lt;&#x2F;span&gt;&lt;span&gt;      64:       00 00 00 00     .word   0x00000000
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;00000068 &amp;lt;tone&amp;gt;:
&lt;&#x2F;span&gt;&lt;span&gt;      68: e9 2d 40 f8   &amp;lt;unknown&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;      6c: e1 a0 40 01   smlaltteq       r10, r0, r1, r0
&lt;&#x2F;span&gt;&lt;span&gt;      70: e1 a0 10 80   andshi  r10, r0, r1, ror #1
&lt;&#x2F;span&gt;&lt;span&gt;      74: e3 a0 09 3d   stclo   p0, c10, [r9, #-908]
&lt;&#x2F;span&gt;&lt;span&gt;      78: e2 80 0d 09   stmdbeq sp, {r1, r5, r6, r7, pc}
&lt;&#x2F;span&gt;&lt;span&gt;      7c: eb ff ff fe   cdp2    p15, #15, c15, c15, c11, #7
&lt;&#x2F;span&gt;&lt;span&gt;      80: e1 a0 60 00   rsbeq   r10, r0, r1, ror #1
&lt;&#x2F;span&gt;&lt;span&gt;      84: e1 a0 00 04   streq   r10, [r0], #-225
&lt;&#x2F;span&gt;&lt;span&gt;      88: e1 a0 10 86   ldrhi   r10, [r0], -r1, ror #1
&lt;&#x2F;span&gt;&lt;span&gt;      8c: eb ff ff fe   cdp2    p15, #15, c15, c15, c11, #7
&lt;&#x2F;span&gt;&lt;span&gt;      90: e2 50 70 00   rsbseq  r5, r0, r2, ror #1
&lt;&#x2F;span&gt;&lt;span&gt;      94: da 00 00 10   ldrdne  r0, r1, [r0], -r10
&lt;&#x2F;span&gt;&lt;span&gt;      98: e3 a0 40 00   subeq   r10, r0, r3, ror #1
&lt;&#x2F;span&gt;&lt;span&gt;      9c: e5 9f 50 3c   mrrclo  p15, #14, r9, r0, c5
&lt;&#x2F;span&gt;&lt;span&gt;      a0: e5 95 30 00   eorseq  r9, r0, r5, ror #11
&lt;&#x2F;span&gt;&lt;span&gt;      a4: e5 93 20 00   eoreq   r9, r0, r5, ror #7
&lt;&#x2F;span&gt;&lt;span&gt;      a8: e3 82 20 10   eorne   r8, r0, r3, ror #5
&lt;&#x2F;span&gt;&lt;span&gt;      ac: e5 83 20 00   eoreq   r8, r0, r5, ror #7
&lt;&#x2F;span&gt;&lt;span&gt;      b0: e1 a0 00 06   streq   r10, [r0], -r1, ror #1
&lt;&#x2F;span&gt;&lt;span&gt;      b4: eb ff ff d1   mvnsle  pc, r11, ror #31
&lt;&#x2F;span&gt;&lt;span&gt;      b8: e5 95 30 00   eorseq  r9, r0, r5, ror #11
&lt;&#x2F;span&gt;&lt;span&gt;      bc: e5 93 20 00   eoreq   r9, r0, r5, ror #7
&lt;&#x2F;span&gt;&lt;span&gt;      c0: e3 c2 20 10   eorne   r12, r0, r3, ror #5
&lt;&#x2F;span&gt;&lt;span&gt;      c4: e5 83 20 00   eoreq   r8, r0, r5, ror #7
&lt;&#x2F;span&gt;&lt;span&gt;      c8: e1 a0 00 06   streq   r10, [r0], -r1, ror #1
&lt;&#x2F;span&gt;&lt;span&gt;      cc: eb ff ff cb   blgt    #-84 &amp;lt;tone+0x18&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;      d0: e2 84 40 01   smlaltteq       r8, r0, r2, r4
&lt;&#x2F;span&gt;&lt;span&gt;      d4: e1 57 00 04   streq   r5, [r0], #-2017
&lt;&#x2F;span&gt;&lt;span&gt;      d8: ca ff ff f0   &amp;lt;unknown&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;      dc: e8 bd 80 f8   &amp;lt;unknown&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;000000e0 &amp;lt;$d&amp;gt;:
&lt;&#x2F;span&gt;&lt;span&gt;      e0:       00 00 00 00     .word   0x00000000
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;000000e4 &amp;lt;call_me_maybe&amp;gt;:
&lt;&#x2F;span&gt;&lt;span&gt;      e4: e9 2d 40 f8   &amp;lt;unknown&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;      e8: e1 a0 60 00   rsbeq   r10, r0, r1, ror #1
&lt;&#x2F;span&gt;&lt;span&gt;      ec: e5 9f 01 98   stmdals r1, {r0, r2, r5, r6, r7, r8, r9, r10, r11, r12, pc}
&lt;&#x2F;span&gt;&lt;span&gt;      f0: eb ff ff fe   cdp2    p15, #15, c15, c15, c11, #7
&lt;&#x2F;span&gt;&lt;span&gt;      f4: e1 a0 00 06   streq   r10, [r0], -r1, ror #1
&lt;&#x2F;span&gt;&lt;span&gt;      f8: eb ff ff c0   rscsgt  pc, pc, r11, ror #31
&lt;&#x2F;span&gt;&lt;span&gt;      fc: e3 a0 0f 62   andvs   r10, pc, #227
&lt;&#x2F;span&gt;&lt;span&gt;     100: e1 a0 10 06   ldreq   r10, [r0], -r1, ror #1
&lt;&#x2F;span&gt;&lt;span&gt;     104: eb ff ff fe   cdp2    p15, #15, c15, c15, c11, #7
&lt;&#x2F;span&gt;&lt;span&gt;     108: e2 86 70 03   cmneq   r0, #236978176
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This shows function names (&lt;code&gt;buzzer_init&lt;&#x2F;code&gt;, &lt;code&gt;tone&lt;&#x2F;code&gt;, &lt;code&gt;call_me_maybe&lt;&#x2F;code&gt;), the offset
and encoding of each instruction, and the more human readable assembly
code corresponding to that instruction. The human readable instructions produced
by this tool look highly suspect to me and I quickly learnt to ignore them and just
focus on the machine code (e.g. the first instruction of &lt;code&gt;buzzer_init&lt;&#x2F;code&gt; is &lt;code&gt;e3 a0 23 3a&lt;&#x2F;code&gt;).&lt;&#x2F;p&gt;
&lt;p&gt;I ended up relying heavily on an &lt;a href=&quot;https:&#x2F;&#x2F;shell-storm.org&#x2F;online&#x2F;Online-Assembler-and-Disassembler&quot;&gt;online ARM
(dis)assembler&lt;&#x2F;a&gt;
for decoding and eventually encoding instructions.&lt;&#x2F;p&gt;
&lt;p&gt;As a sanity check, I disassembled the &lt;code&gt;buzzer_init&lt;&#x2F;code&gt; function since it’s short.
The concatenation of the machine code of its 4 instructions (copied from the
output of &lt;code&gt;llvm-objdump&lt;&#x2F;code&gt; above) is:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;e3 a0 23 3a e5 9f 30 04 e5 83 20 00 e1 2f ff 1e
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Disassembling this gives:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;0x0000000000000000:  E3 A0 23 3A    mov r2, #0xe8000000
&lt;&#x2F;span&gt;&lt;span&gt;0x0000000000000004:  E5 9F 30 04    ldr r3, [pc, #4]
&lt;&#x2F;span&gt;&lt;span&gt;0x0000000000000008:  E5 83 20 00    str r2, [r3]
&lt;&#x2F;span&gt;&lt;span&gt;0x000000000000000c:  E1 2F FF 1E    bx  lr
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The implementation of this function in C is:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;c&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-c &quot;&gt;&lt;code class=&quot;language-c&quot; data-lang=&quot;c&quot;&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;void &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;buzzer_init&lt;&#x2F;span&gt;&lt;span&gt;() {
&lt;&#x2F;span&gt;&lt;span&gt;    VM_BASE_INIT(GPIO_VSTART);
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Expanding the macro and constant:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;c&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-c &quot;&gt;&lt;code class=&quot;language-c&quot; data-lang=&quot;c&quot;&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;void &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;buzzer_init&lt;&#x2F;span&gt;&lt;span&gt;() {
&lt;&#x2F;span&gt;&lt;span&gt;    _global_base &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;0xE8000000&lt;&#x2F;span&gt;&lt;span&gt;;
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The presence of the constant &lt;code&gt;0xE8000000&lt;&#x2F;code&gt; in the C and assembly gave me some
degree of confidence that &lt;code&gt;objdump&lt;&#x2F;code&gt; was correctly locating functions in the
object file and the online assembler could disassemble machine code into
instructions for the slug’s processor architecture.&lt;&#x2F;p&gt;
&lt;p&gt;Observing the instruction offsets in the object file as analyzed by
&lt;code&gt;llvm-objdump&lt;&#x2F;code&gt;, all the functions are consecutive. This invites the question as
to whether they are also consecutive with the same relative offset in the binary
image.&lt;&#x2F;p&gt;
&lt;p&gt;Choosing a prefix of a function’s machine code (taken from the output of
&lt;code&gt;llvm-objdump&lt;&#x2F;code&gt;) which is long enough to be unique
in the binary image, and searching for it with &lt;code&gt;hexedit&lt;&#x2F;code&gt;, I could locate that
function in the binary image. I’ve highlighted the machine code for
&lt;code&gt;buzzer_init&lt;&#x2F;code&gt;, &lt;code&gt;tone&lt;&#x2F;code&gt;, and the beginning of &lt;code&gt;call_me_maybe&lt;&#x2F;code&gt; in red, green, and
blue respectively in the following output from &lt;code&gt;hexedit&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;00032920   E0 D0 30 04  BA FF FF FB  E8 BD 80 F8  &lt;span style=&quot;background-color:red&quot;&gt;E3 A0 23 3A&lt;&#x2F;span&gt;  ..0...........#:
&lt;&#x2F;span&gt;&lt;span&gt;00032930   &lt;span style=&quot;background-color:red&quot;&gt;E5 9F 30 04  E5 83 20 00  E1 2F FF 1E&lt;&#x2F;span&gt;  00 04 C2 50  ..0... ..&#x2F;.....P
&lt;&#x2F;span&gt;&lt;span&gt;00032940   &lt;span style=&quot;background-color:green&quot;&gt;E9 2D 40 F8  E1 A0 40 01  E1 A0 10 80  E3 A0 09 3D&lt;&#x2F;span&gt;  .-@...@........=
&lt;&#x2F;span&gt;&lt;span&gt;00032950   &lt;span style=&quot;background-color:green&quot;&gt;E2 80 0D 09  EB 00 AA 17  E1 A0 60 00  E1 A0 00 04&lt;&#x2F;span&gt;  ..........`.....
&lt;&#x2F;span&gt;&lt;span&gt;00032960   &lt;span style=&quot;background-color:green&quot;&gt;E1 A0 10 86  EB 00 AA 13  E2 50 70 00  DA 00 00 10&lt;&#x2F;span&gt;  .........Pp.....
&lt;&#x2F;span&gt;&lt;span&gt;00032970   &lt;span style=&quot;background-color:green&quot;&gt;E3 A0 40 00  E5 9F 50 3C  E5 95 30 00  E5 93 20 00&lt;&#x2F;span&gt;  ..@...P&amp;lt;..0... .
&lt;&#x2F;span&gt;&lt;span&gt;00032980   &lt;span style=&quot;background-color:green&quot;&gt;E3 82 20 10  E5 83 20 00  E1 A0 00 06  EB FF FF D1&lt;&#x2F;span&gt;  .. ... .........
&lt;&#x2F;span&gt;&lt;span&gt;00032990   &lt;span style=&quot;background-color:green&quot;&gt;E5 95 30 00  E5 93 20 00  E3 C2 20 10  E5 83 20 00&lt;&#x2F;span&gt;  ..0... ... ... .
&lt;&#x2F;span&gt;&lt;span&gt;000329A0   &lt;span style=&quot;background-color:green&quot;&gt;E1 A0 00 06  EB FF FF CB  E2 84 40 01  E1 57 00 04&lt;&#x2F;span&gt;  ..........@..W..
&lt;&#x2F;span&gt;&lt;span&gt;000329B0   &lt;span style=&quot;background-color:green&quot;&gt;CA FF FF F0  E8 BD 80 F8&lt;&#x2F;span&gt;  00 04 C2 50  &lt;span style=&quot;background-color:blue&quot;&gt;E9 2D 40 F8&lt;&#x2F;span&gt;  ...........P.-@.
&lt;&#x2F;span&gt;&lt;span&gt;000329C0   &lt;span style=&quot;background-color:blue&quot;&gt;E1 A0 60 00  E5 9F 01 98  EB 00 16 16  E1 A0 00 06&lt;&#x2F;span&gt;  ..`.............
&lt;&#x2F;span&gt;&lt;span&gt;000329D0   &lt;span style=&quot;background-color:blue&quot;&gt;EB FF FF C0  E3 A0 0F 62  E1 A0 10 06  EB FF FF D7&lt;&#x2F;span&gt;  .......b........
&lt;&#x2F;span&gt;&lt;span&gt;000329E0   &lt;span style=&quot;background-color:blue&quot;&gt;E2 86 70 03  E3 56 00 00  A1 A0 70 06  E1 A0 71 47&lt;&#x2F;span&gt;  ..p..V....p...qG
&lt;&#x2F;span&gt;&lt;span&gt;000329F0   &lt;span style=&quot;background-color:blue&quot;&gt;E3 A0 00 F6  E1 A0 10 07  EB FF FF D0  E0 86 4F A6&lt;&#x2F;span&gt;  ..............O.
&lt;&#x2F;span&gt;&lt;span&gt;00032A00   &lt;span style=&quot;background-color:blue&quot;&gt;E1 A0 40 C4  E3 A0 5F 49  E2 85 50 01  E1 A0 00 05&lt;&#x2F;span&gt;  ..@..._I..P.....
&lt;&#x2F;span&gt;&lt;span&gt;00032A10   &lt;span style=&quot;background-color:blue&quot;&gt;E1 A0 10 04  EB FF FF C9  E3 A0 0F 62  E1 A0 10 07&lt;&#x2F;span&gt;  ...........b....
&lt;&#x2F;span&gt;&lt;span&gt;---  bootimg.bin       --0x32A1C&#x2F;0x7C000--41%----------------------------------
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Cross referencing this with the &lt;code&gt;objdump&lt;&#x2F;code&gt; output it becomes clear that the
layout of this part of bootable image is the same as the object file
&lt;code&gt;buzzer.o&lt;&#x2F;code&gt;. The location in memory of the buzzer functions relative to each
other will be the same as in the object file.&lt;&#x2F;p&gt;
&lt;p&gt;Staring at this for a while I had the idea of rather than working out how to
call &lt;code&gt;call_me_maybe&lt;&#x2F;code&gt; as a function, I could modify the &lt;code&gt;buzzer_init&lt;&#x2F;code&gt; function to jump directly to the
beginning of &lt;code&gt;call_me_maybe&lt;&#x2F;code&gt; instead of returning. When the &lt;code&gt;main&lt;&#x2F;code&gt; function
calls &lt;code&gt;buzzer_init&lt;&#x2F;code&gt;, that would then cause the song to play immediately rather
than requiring a separate call to &lt;code&gt;call_me_maybe&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;ARM assembly has a &lt;code&gt;b&lt;&#x2F;code&gt;
instruction which jumps to a nearby address specified by a relative offset. So
if we’re replacing the final instruction of &lt;code&gt;buzzer_init&lt;&#x2F;code&gt; to jump to the
beginning of &lt;code&gt;call_me_maybe&lt;&#x2F;code&gt;, that’s a relative offset of &lt;code&gt;0xE4 - 0x60 = 0x84&lt;&#x2F;code&gt;.
The instruction &lt;code&gt;b #0x84&lt;&#x2F;code&gt; encodes to the machine code &lt;code&gt;EA 00 00 1F&lt;&#x2F;code&gt;, so I used
&lt;code&gt;hexedit&lt;&#x2F;code&gt; to replace the final instruction of &lt;code&gt;buzzer_init&lt;&#x2F;code&gt; (in red above)
with &lt;code&gt;EA 00 00 1F&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;This shows the same section of the binary as above, with the updated 4 bytes in
yellow:&lt;&#x2F;p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;00032920   E0 D0 30 04  BA FF FF FB  E8 BD 80 F8  &lt;span style=&quot;background-color:red&quot;&gt;E3 A0 23 3A&lt;&#x2F;span&gt;  ..0...........#:
&lt;&#x2F;span&gt;&lt;span&gt;00032930   &lt;span style=&quot;background-color:red&quot;&gt;E5 9F 30 04  E5 83 20 00  &lt;span style=&quot;background-color:yellow&quot;&gt;EA 00 00 1F&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;  00 04 C2 50  ..0... ..&#x2F;.....P
&lt;&#x2F;span&gt;&lt;span&gt;00032940   &lt;span style=&quot;background-color:green&quot;&gt;E9 2D 40 F8  E1 A0 40 01  E1 A0 10 80  E3 A0 09 3D&lt;&#x2F;span&gt;  .-@...@........=
&lt;&#x2F;span&gt;&lt;span&gt;00032950   &lt;span style=&quot;background-color:green&quot;&gt;E2 80 0D 09  EB 00 AA 17  E1 A0 60 00  E1 A0 00 04&lt;&#x2F;span&gt;  ..........`.....
&lt;&#x2F;span&gt;&lt;span&gt;00032960   &lt;span style=&quot;background-color:green&quot;&gt;E1 A0 10 86  EB 00 AA 13  E2 50 70 00  DA 00 00 10&lt;&#x2F;span&gt;  .........Pp.....
&lt;&#x2F;span&gt;&lt;span&gt;00032970   &lt;span style=&quot;background-color:green&quot;&gt;E3 A0 40 00  E5 9F 50 3C  E5 95 30 00  E5 93 20 00&lt;&#x2F;span&gt;  ..@...P&amp;lt;..0... .
&lt;&#x2F;span&gt;&lt;span&gt;00032980   &lt;span style=&quot;background-color:green&quot;&gt;E3 82 20 10  E5 83 20 00  E1 A0 00 06  EB FF FF D1&lt;&#x2F;span&gt;  .. ... .........
&lt;&#x2F;span&gt;&lt;span&gt;00032990   &lt;span style=&quot;background-color:green&quot;&gt;E5 95 30 00  E5 93 20 00  E3 C2 20 10  E5 83 20 00&lt;&#x2F;span&gt;  ..0... ... ... .
&lt;&#x2F;span&gt;&lt;span&gt;000329A0   &lt;span style=&quot;background-color:green&quot;&gt;E1 A0 00 06  EB FF FF CB  E2 84 40 01  E1 57 00 04&lt;&#x2F;span&gt;  ..........@..W..
&lt;&#x2F;span&gt;&lt;span&gt;000329B0   &lt;span style=&quot;background-color:green&quot;&gt;CA FF FF F0  E8 BD 80 F8&lt;&#x2F;span&gt;  00 04 C2 50  &lt;span style=&quot;background-color:blue&quot;&gt;E9 2D 40 F8&lt;&#x2F;span&gt;  ...........P.-@.
&lt;&#x2F;span&gt;&lt;span&gt;000329C0   &lt;span style=&quot;background-color:blue&quot;&gt;E1 A0 60 00  E5 9F 01 98  EB 00 16 16  E1 A0 00 06&lt;&#x2F;span&gt;  ..`.............
&lt;&#x2F;span&gt;&lt;span&gt;000329D0   &lt;span style=&quot;background-color:blue&quot;&gt;EB FF FF C0  E3 A0 0F 62  E1 A0 10 06  EB FF FF D7&lt;&#x2F;span&gt;  .......b........
&lt;&#x2F;span&gt;&lt;span&gt;000329E0   &lt;span style=&quot;background-color:blue&quot;&gt;E2 86 70 03  E3 56 00 00  A1 A0 70 06  E1 A0 71 47&lt;&#x2F;span&gt;  ..p..V....p...qG
&lt;&#x2F;span&gt;&lt;span&gt;000329F0   &lt;span style=&quot;background-color:blue&quot;&gt;E3 A0 00 F6  E1 A0 10 07  EB FF FF D0  E0 86 4F A6&lt;&#x2F;span&gt;  ..............O.
&lt;&#x2F;span&gt;&lt;span&gt;00032A00   &lt;span style=&quot;background-color:blue&quot;&gt;E1 A0 40 C4  E3 A0 5F 49  E2 85 50 01  E1 A0 00 05&lt;&#x2F;span&gt;  ..@..._I..P.....
&lt;&#x2F;span&gt;&lt;span&gt;00032A10   &lt;span style=&quot;background-color:blue&quot;&gt;E1 A0 10 04  EB FF FF C9  E3 A0 0F 62  E1 A0 10 07&lt;&#x2F;span&gt;  ...........b....
&lt;&#x2F;span&gt;&lt;span&gt;---  bootimg.bin       --0x32A1C&#x2F;0x7C000--41%----------------------------------
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;And running the result:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;[gpio] starting
&lt;&#x2F;span&gt;&lt;span&gt;[gpio] found region 0x001d13a8
&lt;&#x2F;span&gt;&lt;span&gt;[gpio] paging success
&lt;&#x2F;span&gt;&lt;span&gt;Hey I just met you
&lt;&#x2F;span&gt;&lt;span&gt;And this is crazy
&lt;&#x2F;span&gt;&lt;span&gt;But here&amp;#39;s my number
&lt;&#x2F;span&gt;&lt;span&gt;So call me maybe
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;Starting &amp;quot;tty_test&amp;quot;...
&lt;&#x2F;span&gt;&lt;span&gt;Loading first process.  Trying to set up a page table!
&lt;&#x2F;span&gt;&lt;span&gt;...
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Hacker voice: &lt;em&gt;I’m in.&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
&lt;p&gt;We’re successfully jumping into the &lt;code&gt;call_me_maybe&lt;&#x2F;code&gt; function as evidence by the
printouts, but it’s not playing any sound yet. Here’s the C code again for
convenience:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;c&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-c &quot;&gt;&lt;code class=&quot;language-c&quot; data-lang=&quot;c&quot;&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;void &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;call_me_maybe&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;int &lt;&#x2F;span&gt;&lt;span&gt;beat_len) {
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;printf&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;Hey I just met you&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;\n&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;);
&lt;&#x2F;span&gt;&lt;span&gt;    udelay(beat_len);
&lt;&#x2F;span&gt;&lt;span&gt;    tone(NOTE_G4, beat_len);
&lt;&#x2F;span&gt;&lt;span&gt;    tone(NOTE_B3, beat_len&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;4&lt;&#x2F;span&gt;&lt;span&gt;);
&lt;&#x2F;span&gt;&lt;span&gt;    tone(NOTE_D4, beat_len&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;2&lt;&#x2F;span&gt;&lt;span&gt;);
&lt;&#x2F;span&gt;&lt;span&gt;    tone(NOTE_G4, beat_len&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;4&lt;&#x2F;span&gt;&lt;span&gt;);
&lt;&#x2F;span&gt;&lt;span&gt;    tone(NOTE_G4, beat_len&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;4&lt;&#x2F;span&gt;&lt;span&gt;);
&lt;&#x2F;span&gt;&lt;span&gt;    tone(NOTE_D4, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;3&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;*&lt;&#x2F;span&gt;&lt;span&gt;beat_len&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;4&lt;&#x2F;span&gt;&lt;span&gt;);
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;printf&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;And this is crazy&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;\n&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;);
&lt;&#x2F;span&gt;&lt;span&gt;    udelay(beat_len);
&lt;&#x2F;span&gt;&lt;span&gt;    udelay(beat_len&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;2&lt;&#x2F;span&gt;&lt;span&gt;);
&lt;&#x2F;span&gt;&lt;span&gt;    tone(NOTE_B3, beat_len&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;2&lt;&#x2F;span&gt;&lt;span&gt;);
&lt;&#x2F;span&gt;&lt;span&gt;    tone(NOTE_B3, beat_len&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;4&lt;&#x2F;span&gt;&lt;span&gt;);
&lt;&#x2F;span&gt;&lt;span&gt;    tone(NOTE_D4, beat_len&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;2&lt;&#x2F;span&gt;&lt;span&gt;);
&lt;&#x2F;span&gt;&lt;span&gt;    tone(NOTE_B4, beat_len&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;4&lt;&#x2F;span&gt;&lt;span&gt;);
&lt;&#x2F;span&gt;&lt;span&gt;    tone(NOTE_B4, beat_len&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;2&lt;&#x2F;span&gt;&lt;span&gt;);
&lt;&#x2F;span&gt;&lt;span&gt;    tone(NOTE_G4, beat_len&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;2&lt;&#x2F;span&gt;&lt;span&gt;);
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;printf&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;But here&amp;#39;s my number&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;\n&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;);
&lt;&#x2F;span&gt;&lt;span&gt;    udelay(beat_len);
&lt;&#x2F;span&gt;&lt;span&gt;    udelay(beat_len&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;2&lt;&#x2F;span&gt;&lt;span&gt;);
&lt;&#x2F;span&gt;&lt;span&gt;    tone(NOTE_G4, beat_len&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;2&lt;&#x2F;span&gt;&lt;span&gt;);
&lt;&#x2F;span&gt;&lt;span&gt;    tone(NOTE_B4, beat_len&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;2&lt;&#x2F;span&gt;&lt;span&gt;);
&lt;&#x2F;span&gt;&lt;span&gt;    tone(NOTE_C5, beat_len&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;2&lt;&#x2F;span&gt;&lt;span&gt;);
&lt;&#x2F;span&gt;&lt;span&gt;    tone(NOTE_B4, beat_len&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;2&lt;&#x2F;span&gt;&lt;span&gt;);
&lt;&#x2F;span&gt;&lt;span&gt;    tone(NOTE_G4, beat_len&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;2&lt;&#x2F;span&gt;&lt;span&gt;);
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;printf&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;So call me maybe&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;\n&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;);
&lt;&#x2F;span&gt;&lt;span&gt;    udelay(beat_len);
&lt;&#x2F;span&gt;&lt;span&gt;    udelay(beat_len&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;2&lt;&#x2F;span&gt;&lt;span&gt;);
&lt;&#x2F;span&gt;&lt;span&gt;    tone(NOTE_G4, beat_len&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;2&lt;&#x2F;span&gt;&lt;span&gt;);
&lt;&#x2F;span&gt;&lt;span&gt;    tone(NOTE_B4, beat_len&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;2&lt;&#x2F;span&gt;&lt;span&gt;);
&lt;&#x2F;span&gt;&lt;span&gt;    tone(NOTE_A4, beat_len&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;2&lt;&#x2F;span&gt;&lt;span&gt;);
&lt;&#x2F;span&gt;&lt;span&gt;    tone(NOTE_A4, beat_len&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;2&lt;&#x2F;span&gt;&lt;span&gt;);
&lt;&#x2F;span&gt;&lt;span&gt;    tone(NOTE_G4, beat_len&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;2&lt;&#x2F;span&gt;&lt;span&gt;);
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Normally this function gets passed a &lt;code&gt;beat_len&lt;&#x2F;code&gt; argument specifying the duration of each
beat in microseconds. But now that this function isn’t being called but rather &lt;em&gt;jumped into&lt;&#x2F;em&gt;,
whatever processor register stores the beat length isn’t being initialized to anything in
particular. Its value will be whatever it was before jumping into this function.
Probably this values is very low, possibly 0, so the buzzes are too short to be
heard, hence the silence. More evidence for this theory is that all the song
lyrics are printed immediately, rather than with a noticeable delay in between.&lt;&#x2F;p&gt;
&lt;p&gt;Let’s take a look at the disassembly of the beginning of &lt;code&gt;call_me_maybe&lt;&#x2F;code&gt; which I’ve manually annotated:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;0x0000000000000000:  E9 2D 40 F8    push  {r3, r4, r5, r6, r7, lr}
&lt;&#x2F;span&gt;&lt;span&gt;0x0000000000000004:  E1 A0 60 00    mov   r6, r0      }- save the beat_len arg in r6
&lt;&#x2F;span&gt;&lt;span&gt;0x0000000000000008:  E5 9F 01 98    ldr   r0, [pc, #0x198]
&lt;&#x2F;span&gt;&lt;span&gt;0x000000000000000c:  EB 00 16 16    bl    #0x586c     }- call printf
&lt;&#x2F;span&gt;&lt;span&gt;0x0000000000000010:  E1 A0 00 06    mov   r0, r6      }- arg for udelay
&lt;&#x2F;span&gt;&lt;span&gt;0x0000000000000014:  EB FF FF C0    bl    #0xffffff1c }- call udelay
&lt;&#x2F;span&gt;&lt;span&gt;0x0000000000000018:  E3 A0 0F 62    mov   r0, #0x188  }- first arg for tone (note G4)
&lt;&#x2F;span&gt;&lt;span&gt;0x000000000000001c:  E1 A0 10 06    mov   r1, r6      }- second arg for tone (beat_len)
&lt;&#x2F;span&gt;&lt;span&gt;0x0000000000000020:  EB FF FF D7    bl    #0xffffff84 }- call tone
&lt;&#x2F;span&gt;&lt;span&gt;0x0000000000000024:  E2 86 70 03    add   r7, r6, #3
&lt;&#x2F;span&gt;&lt;span&gt;0x0000000000000028:  E3 56 00 00    cmp   r6, #0
&lt;&#x2F;span&gt;&lt;span&gt;0x000000000000002c:  A1 A0 70 06    movge r7, r6
&lt;&#x2F;span&gt;&lt;span&gt;0x0000000000000030:  E1 A0 71 47    asr   r7, r7, #2
&lt;&#x2F;span&gt;&lt;span&gt;0x0000000000000034:  E3 A0 00 F6    mov   r0, #0xf6   }- first arg for tone (note B3)
&lt;&#x2F;span&gt;&lt;span&gt;0x0000000000000038:  E1 A0 10 07    mov   r1, r7
&lt;&#x2F;span&gt;&lt;span&gt;0x000000000000003c:  EB FF FF D0    bl    #0xffffff84 }- call tone
&lt;&#x2F;span&gt;&lt;span&gt;0x0000000000000040:  E0 86 4F A6    add   r4, r6, r6, lsr #31
&lt;&#x2F;span&gt;&lt;span&gt;0x0000000000000044:  E1 A0 40 C4    asr   r4, r4, #1
&lt;&#x2F;span&gt;&lt;span&gt;0x0000000000000048:  E3 A0 5F 49    mov   r5, #0x124  }
&lt;&#x2F;span&gt;&lt;span&gt;0x000000000000004c:  E2 85 50 01    add   r5, r5, #1  }- first arg for tone (note D4)
&lt;&#x2F;span&gt;&lt;span&gt;0x0000000000000050:  E1 A0 00 05    mov   r0, r5      }
&lt;&#x2F;span&gt;&lt;span&gt;0x0000000000000054:  E1 A0 10 04    mov   r1, r4
&lt;&#x2F;span&gt;&lt;span&gt;0x0000000000000058:  EB FF FF C9    bl    #0xffffff84 }- call tone
&lt;&#x2F;span&gt;&lt;span&gt;0x000000000000005c:  E3 A0 0F 62    mov   r0, #0x188  }- first arg for tone (note G4)
&lt;&#x2F;span&gt;&lt;span&gt;0x0000000000000060:  E1 A0 10 07    mov   r1, r7
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;It appears &lt;code&gt;r0&lt;&#x2F;code&gt; is being used to pass the first
argument to functions (and &lt;code&gt;r1&lt;&#x2F;code&gt; is used to pass the second argument).&lt;&#x2F;p&gt;
&lt;p&gt;To build some confidence in this hypothesis, look at the signature for the &lt;code&gt;tone&lt;&#x2F;code&gt; function:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;c&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-c &quot;&gt;&lt;code class=&quot;language-c&quot; data-lang=&quot;c&quot;&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;void &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;tone&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;int &lt;&#x2F;span&gt;&lt;span&gt;frequency, &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;int &lt;&#x2F;span&gt;&lt;span&gt;time);
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Throughout the assembly of &lt;code&gt;call_me_maybe&lt;&#x2F;code&gt; we see &lt;code&gt;r0&lt;&#x2F;code&gt; being set to a
representation of the note frequency and &lt;code&gt;r1&lt;&#x2F;code&gt; being passed the note duration right before each &lt;code&gt;bl #0xffffff84&lt;&#x2F;code&gt;
instruction, which looks like calls to the &lt;code&gt;tone&lt;&#x2F;code&gt; function.&lt;&#x2F;p&gt;
&lt;p&gt;The second line &lt;code&gt;mov   r6, r0&lt;&#x2F;code&gt; is saving the beat
length (&lt;code&gt;call_me_maybe&lt;&#x2F;code&gt;’s first and only argument) to &lt;code&gt;r6&lt;&#x2F;code&gt; before using &lt;code&gt;r0&lt;&#x2F;code&gt; for some other
purpose (to pass the argument to &lt;code&gt;printf&lt;&#x2F;code&gt;). To increase the beat length we need to replace this second instruction
with an instruction that stores a higher value in &lt;code&gt;r6&lt;&#x2F;code&gt;. This has to be done with
a single instruction, since the first instruction of this function is important
in maintaining the function calling convention (otherwise the OS would crash
when &lt;code&gt;call_me_maybe&lt;&#x2F;code&gt; returned), and the third instruction is necessary for
printing “Hey I just met you”. And remember we can’t insert or remove bytes -
only update them in place.&lt;&#x2F;p&gt;
&lt;p&gt;I found that the original value of &lt;code&gt;r6&lt;&#x2F;code&gt; (before the second instruction set it to
the contents of &lt;code&gt;r0&lt;&#x2F;code&gt;) was non-zero, as replacing the &lt;code&gt;mov r6, r0&lt;&#x2F;code&gt; with an instruction that does nothing
(such as &lt;code&gt;mov r0, r0&lt;&#x2F;code&gt;) caused the song to play, albeit too fast. But that
indicates this approach will definitely work!&lt;&#x2F;p&gt;
&lt;p&gt;Rather than doing nothing, I need the second instruction to increase the
existing value of &lt;code&gt;r6&lt;&#x2F;code&gt; by some amount. There are several approaches I could have
taken, but I found that simply doubling its value by shifting it by one bit to
the left gives a suitable beat length. I replaced the second instruction
with &lt;code&gt;lsl r6, r6, #1&lt;&#x2F;code&gt; which assembles to &lt;code&gt;E1 A0 60 86&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Here’s the result:&lt;&#x2F;p&gt;
&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https:&#x2F;&#x2F;www.youtube.com&#x2F;embed&#x2F;s_51do9YbHw?si=Fpm21KF1jjAWMK0y&quot; title=&quot;YouTube video player&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&quot; referrerpolicy=&quot;strict-origin-when-cross-origin&quot; allowfullscreen&gt;&lt;&#x2F;iframe&gt;
&lt;p&gt;What strikes me about this project is that I worked on it in a time before I
started compulsively uploading all the code I wrote to a code forge like github.
This was for a university assignment so perhaps there was a policy against
that sort of thing. At this time github was still relatively new and I wasn’t
very familiar with version control, so it sometimes seemed too complex to be
worth using it for every project.&lt;&#x2F;p&gt;
&lt;p&gt;Nowadays I can’t imagine a workflow that doesn’t involve regular
commits to a version control system and it’s so easy to push all my code to
github that I doubt I’ll ever lose access to code I wrote since 2015 or so.
I horde everything in the off chance that it will be useful or at least
interesting to someone in the future.&lt;&#x2F;p&gt;
&lt;p&gt;Since this assignment was group work we did use version control to manage
changes. My copy of the source still contains version control metadata and I can
see we used my assignment partner’s user account on the university server as our
centralized repository, and I doubt that’s archived anywhere.
The last commit in my copy of the project is from midway through the semester.
Fortunately the copy I made on my account was taken after I added the Easter
egg.&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;changeset:   41:1329df276e9a
&lt;&#x2F;span&gt;&lt;span&gt;parent:      35:29e1993e26ba
&lt;&#x2F;span&gt;&lt;span&gt;user:        Stephen Sherratt &amp;lt;ssteve@cse.unsw.edu.au&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;date:        Sun Jul 29 16:25:22 2012 +1000
&lt;&#x2F;span&gt;&lt;span&gt;summary:     wrote function that plays Call Me Maybe
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
</description>
      </item>
      <item>
          <title>Sound on OCaml on Windows</title>
          <pubDate>Mon, 02 Jun 2025 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/sound-on-ocaml-on-windows/</link>
          <guid>https://www.gridbugs.org/sound-on-ocaml-on-windows/</guid>
          <description xml:base="https://www.gridbugs.org/sound-on-ocaml-on-windows/">&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;sound-on-ocaml-on-windows&#x2F;banner.jpg&quot; alt=&quot;Close up of a modular synthesizer&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;em&gt;I got my OCaml music synthesizer working on Windows. Here’s all the things that went wrong in the process.&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
&lt;p&gt;This post will chronicle my attempt to use OCaml to play generated audio
samples on a Windows PC. This is part of my work on the &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;gridbugs&#x2F;llama&#x2F;&quot;&gt;llama synthesizer
library&lt;&#x2F;a&gt;, where I’ve been rewriting the
low-level audio logic formerly written in Rust (with
&lt;a href=&quot;https:&#x2F;&#x2F;crates.io&#x2F;crates&#x2F;cpal&quot;&gt;cpal&lt;&#x2F;a&gt;) into OCaml to simplify the build process
and development experience. I’ve written down some of my complaints about
the developer experience working on &lt;code&gt;llama&lt;&#x2F;code&gt; in a &lt;a href=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;frustrating-interactions-with-the-ocaml-ecosystem-while-developing-a-synthesizer-library&#x2F;&quot;&gt;previous
post&lt;&#x2F;a&gt;
and some of those difficulties came from complications working with Rust&#x2F;OCaml
interop, so removing all Rust from the project will help reduce friction
developing it further.&lt;&#x2F;p&gt;
&lt;p&gt;I have a proof-of-concept version of &lt;code&gt;llama&lt;&#x2F;code&gt; based on &lt;a href=&quot;https:&#x2F;&#x2F;opam.ocaml.org&#x2F;packages&#x2F;ao&#x2F;&quot;&gt;OCaml’s libao
bindings&lt;&#x2F;a&gt; which works on Linux and MacOS.
Libao claims to support Windows but I haven’t figured out how to install it on
Windows so I might experiment with some easier-to-install alternatives in this post.&lt;&#x2F;p&gt;
&lt;p&gt;The intention of this post is to serve as a guide to anyone getting started with
OCaml on Windows, to highlight some of the remaining problems with the OCaml ecosystem on
Windows, and to demonstrate some usability issues with Opam in general. My summary is that
OCaml on Windows is easier than I expected it to be, but there are still some
rough edges that newcomers should be aware of to properly set expectations. I
wrote down everything I tried as I tried it, so all the mistakes, blind alleys
and failed debugging experiments will be documented here as well as the paths
that eventually led to solutions.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;install-opam&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#install-opam&quot; aria-label=&quot;Anchor link for: install-opam&quot;&gt;Install Opam&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;For the purposes of this post I’ll set up an entire OCaml environment on Windows
from scratch. I’ll be using PowerShell 7.5.1 for all shell commands in this
post. For the purposes of translating commands and paths to other machines, I’ll
state explicitly that my username is “s”.&lt;&#x2F;p&gt;
&lt;p&gt;We’ll install Opam with WinGet. I’ve had some difficulty setting up WinGet in
the past and written about setting it up properly in a &lt;a href=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;setting-up-winget-for-the-first-time&#x2F;&quot;&gt;previous
post&lt;&#x2F;a&gt; but today it worked as expected.&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;PS C:\Users\s&amp;gt; winget install Git.Git OCaml.opam
&lt;&#x2F;span&gt;&lt;span&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;PS C:\Users\s&amp;gt; opam init
&lt;&#x2F;span&gt;&lt;span&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;How should opam obtain Unix tools?
&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt; 1. Automatically create an internal Cygwin installation that will be managed by
&lt;&#x2F;span&gt;&lt;span&gt;     opam (recommended)
&lt;&#x2F;span&gt;&lt;span&gt;  2. Use an existing Cygwin&#x2F;MSYS2 installation
&lt;&#x2F;span&gt;&lt;span&gt;  3. Abort initialisation
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;[1&#x2F;2&#x2F;3] 1  &amp;lt;-- here I selected option 1
&lt;&#x2F;span&gt;&lt;span&gt;...
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Allowing Opam to manage its own Cygwin installation is the recommended way of
using Opam on Windows, so that’s what I’ll try first. This results in Cygwin being
installed to &lt;code&gt;C:\Users\s\AppData\Local\opam\.cygwin&lt;&#x2F;code&gt;. This might come in handy
to know if we ever need to manually install a Cygwin package, though hopefully
Opam can take care of installing any such packages via Opam’s &lt;code&gt;depexts&lt;&#x2F;code&gt; system
which knows how to install system-specific packages needed by Opam packages by
directly invoking the package manager to install the appropriately-named package.&lt;&#x2F;p&gt;
&lt;p&gt;Bear in mind that Opam’s initialization can take 15 minutes or so on Windows
with most of the time spent building the OCaml compiler. Compiling OCaml code
on Windows tends to be much slower than on MacOS and Linux. I don’t understand
why.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;install-a-sound-library&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#install-a-sound-library&quot; aria-label=&quot;Anchor link for: install-a-sound-library&quot;&gt;Install a sound library&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;We need to choose a library in the Opam repository that can give us access
to the sound card. Ideally we would use a library with a &lt;code&gt;depext&lt;&#x2F;code&gt; tailored to
Windows with Cygwin, as that way Opam can take care of installing any system dependencies
via Cygwin. Since my experimental version of &lt;code&gt;llama&lt;&#x2F;code&gt; is already implemented with
libao that would be the most convenient choice, however its &lt;code&gt;depexts&lt;&#x2F;code&gt; don’t look
promising:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;PS C:\Users\s&amp;gt; opam show conf-ao
&lt;&#x2F;span&gt;&lt;span&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;depexts     [&amp;quot;libao-devel&amp;quot;]
&lt;&#x2F;span&gt;&lt;span&gt;              {os-distribution = &amp;quot;centos&amp;quot; | os-family = &amp;quot;fedora&amp;quot; | os-family = &amp;quot;suse&amp;quot; |
&lt;&#x2F;span&gt;&lt;span&gt;               os-family = &amp;quot;opensuse&amp;quot;}
&lt;&#x2F;span&gt;&lt;span&gt;            [&amp;quot;libao&amp;quot;]
&lt;&#x2F;span&gt;&lt;span&gt;              {os = &amp;quot;freebsd&amp;quot; | os = &amp;quot;macos&amp;quot; &amp;amp; os-distribution = &amp;quot;homebrew&amp;quot; |
&lt;&#x2F;span&gt;&lt;span&gt;               os-distribution = &amp;quot;nixos&amp;quot; |
&lt;&#x2F;span&gt;&lt;span&gt;               os-family = &amp;quot;arch&amp;quot;}
&lt;&#x2F;span&gt;&lt;span&gt;            [&amp;quot;libao-dev&amp;quot;]
&lt;&#x2F;span&gt;&lt;span&gt;              {os-family = &amp;quot;debian&amp;quot; | os-family = &amp;quot;ubuntu&amp;quot; | os-distribution = &amp;quot;alpine&amp;quot;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;There are no entries in that list for Windows, so using libao would require
setting it up manually which I’d prefer to avoid.&lt;&#x2F;p&gt;
&lt;p&gt;Another candidate library is
&lt;a href=&quot;https:&#x2F;&#x2F;opam.ocaml.org&#x2F;packages&#x2F;portaudio&#x2F;&quot;&gt;portaudio&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;PS C:\Users\s&amp;gt; opam show conf-portaudio
&lt;&#x2F;span&gt;&lt;span&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;depexts     [&amp;quot;portaudio-dev&amp;quot;] {os-distribution = &amp;quot;alpine&amp;quot;}
&lt;&#x2F;span&gt;&lt;span&gt;            [&amp;quot;portaudio-devel&amp;quot;]
&lt;&#x2F;span&gt;&lt;span&gt;              {os-distribution = &amp;quot;centos&amp;quot; | os-distribution = &amp;quot;fedora&amp;quot; |
&lt;&#x2F;span&gt;&lt;span&gt;               os-family = &amp;quot;suse&amp;quot; |
&lt;&#x2F;span&gt;&lt;span&gt;               os-family = &amp;quot;opensuse&amp;quot;}
&lt;&#x2F;span&gt;&lt;span&gt;            [&amp;quot;portaudio&amp;quot;] {os = &amp;quot;macos&amp;quot; &amp;amp; os-distribution = &amp;quot;homebrew&amp;quot;}
&lt;&#x2F;span&gt;&lt;span&gt;            [&amp;quot;portaudio&amp;quot;]
&lt;&#x2F;span&gt;&lt;span&gt;              {os = &amp;quot;freebsd&amp;quot; | os-family = &amp;quot;arch&amp;quot; | os-distribution = &amp;quot;nixos&amp;quot; |
&lt;&#x2F;span&gt;&lt;span&gt;               os-distribution = &amp;quot;ol&amp;quot;}
&lt;&#x2F;span&gt;&lt;span&gt;            [&amp;quot;portaudio&amp;quot;] {os = &amp;quot;win32&amp;quot; &amp;amp; os-distribution = &amp;quot;cygwinports&amp;quot;}
&lt;&#x2F;span&gt;&lt;span&gt;            [&amp;quot;portaudio19-dev&amp;quot;] {os-family = &amp;quot;debian&amp;quot; | os-family = &amp;quot;ubuntu&amp;quot;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;That’s more promising as there’s a &lt;code&gt;depext&lt;&#x2F;code&gt; that gets chosen on &lt;code&gt;{os = &quot;win32&quot; &amp;amp; os-distribution = &quot;cygwinports&quot;}&lt;&#x2F;code&gt;. It’s a little concerning that the name of the
&lt;code&gt;os-distribution&lt;&#x2F;code&gt; is “cygwinports” and not just “cygwin”. I’ve never come across
cygwinports before, but maybe it won’t matter. The next step is to see if
installing &lt;code&gt;conf-portaudio&lt;&#x2F;code&gt; with Opam causes the right &lt;code&gt;depext&lt;&#x2F;code&gt; to be installed
with Cygwin:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;PS C:\Users\s&amp;gt; opam install conf-portaudio
&lt;&#x2F;span&gt;&lt;span&gt;The following actions will be performed:
&lt;&#x2F;span&gt;&lt;span&gt;=== recompile 1 package
&lt;&#x2F;span&gt;&lt;span&gt;  ↻ mingw-w64-shims 0.2.0 [uses conf-pkg-config]
&lt;&#x2F;span&gt;&lt;span&gt;=== install 2 packages
&lt;&#x2F;span&gt;&lt;span&gt;  ∗ conf-pkg-config 4     [required by conf-portaudio]
&lt;&#x2F;span&gt;&lt;span&gt;  ∗ conf-portaudio  1
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;Proceed with ↻ 1 recompilation and ∗ 2 installations? [y&#x2F;n] y
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;The following system packages will first need to be installed:
&lt;&#x2F;span&gt;&lt;span&gt;    pkgconf
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt; Handling external dependencies &amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;  🐫
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;+ C:\Users\s\AppData\Local\opam\.cygwin\setup-x86_64.exe &amp;quot;--root&amp;quot; &amp;quot;C:\\Users\\s\\AppData\\Local\\opam\\.cygwin\\root&amp;quot; &amp;quot;--quiet-mode&amp;quot; &amp;quot;noinput&amp;quot; &amp;quot;--no-shortcuts&amp;quot; &amp;quot;--no-startmenu&amp;quot; &amp;quot;--no-desktop&amp;quot; &amp;quot;--no-admin&amp;quot; &amp;quot;--no-version-check&amp;quot; &amp;quot;--no-write-registry&amp;quot; &amp;quot;--packages&amp;quot; &amp;quot;pkgconf&amp;quot; &amp;quot;--upgrade-also&amp;quot; &amp;quot;--only-site&amp;quot; &amp;quot;--site&amp;quot; &amp;quot;https:&#x2F;&#x2F;cygwin.mirror.constant.com&#x2F;&amp;quot; &amp;quot;--local-package-dir&amp;quot; &amp;quot;C:\\Users\\s\\AppData\\Local\\opam\\.cygwin\\cache&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;- Starting cygwin install, version 2.934
&lt;&#x2F;span&gt;&lt;span&gt;- User has NO backup&#x2F;restore rights
&lt;&#x2F;span&gt;&lt;span&gt;- User has NO symlink creation right
&lt;&#x2F;span&gt;&lt;span&gt;- io_stream_cygfile: fopen(&#x2F;etc&#x2F;setup&#x2F;setup.rc) failed 2 No such file or directory
&lt;&#x2F;span&gt;&lt;span&gt;- Current Directory: C:\Users\s\AppData\Local\opam\.cygwin\cache
&lt;&#x2F;span&gt;&lt;span&gt;- root: C:\Users\s\AppData\Local\opam\.cygwin\root user
&lt;&#x2F;span&gt;&lt;span&gt;- Changing gid back to original
&lt;&#x2F;span&gt;&lt;span&gt;- Selected local directory: C:\Users\s\AppData\Local\opam\.cygwin\cache
&lt;&#x2F;span&gt;&lt;span&gt;- net: Preconfig
&lt;&#x2F;span&gt;&lt;span&gt;- site: https:&#x2F;&#x2F;cygwin.mirror.constant.com&#x2F;
&lt;&#x2F;span&gt;&lt;span&gt;- solving: 1 tasks, update: yes, use test packages: no
&lt;&#x2F;span&gt;&lt;span&gt;- solving: 2 tasks, update: no, use test packages: no
&lt;&#x2F;span&gt;&lt;span&gt;- Augmented Transaction List:
&lt;&#x2F;span&gt;&lt;span&gt;-    0 install libpkgconf6 2.4.3-1
&lt;&#x2F;span&gt;&lt;span&gt;-    1 install pkgconf     2.4.3-1
&lt;&#x2F;span&gt;&lt;span&gt;- Downloaded C:\Users\s\AppData\Local\opam\.cygwin\cache&#x2F;https%3a%2f%2fcygwin.mirror.constant.com%2f&#x2F;x86_64&#x2F;release&#x2F;pkgconf&#x2F;libpkgconf6&#x2F;libpkgconf6-2.4.3-1.tar.zst
&lt;&#x2F;span&gt;&lt;span&gt;- Downloaded C:\Users\s\AppData\Local\opam\.cygwin\cache&#x2F;https%3a%2f%2fcygwin.mirror.constant.com%2f&#x2F;x86_64&#x2F;release&#x2F;pkgconf&#x2F;pkgconf-2.4.3-1.tar.zst
&lt;&#x2F;span&gt;&lt;span&gt;- Extracting from file:&#x2F;&#x2F;C:\Users\s\AppData\Local\opam\.cygwin\cache&#x2F;https%3a%2f%2fcygwin.mirror.constant.com%2f&#x2F;x86_64&#x2F;release&#x2F;pkgconf&#x2F;libpkgconf6&#x2F;libpkgconf6-2.4.3-1.tar.zst
&lt;&#x2F;span&gt;&lt;span&gt;- Extracting from file:&#x2F;&#x2F;C:\Users\s\AppData\Local\opam\.cygwin\cache&#x2F;https%3a%2f%2fcygwin.mirror.constant.com%2f&#x2F;x86_64&#x2F;release&#x2F;pkgconf&#x2F;pkgconf-2.4.3-1.tar.zst
&lt;&#x2F;span&gt;&lt;span&gt;- running: C:\Users\s\AppData\Local\opam\.cygwin\root\bin\dash.exe &amp;quot;&#x2F;etc&#x2F;postinstall&#x2F;0p_000_autorebase.dash&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;- running: C:\Users\s\AppData\Local\opam\.cygwin\root\bin\dash.exe &amp;quot;&#x2F;etc&#x2F;postinstall&#x2F;0p_update-info-dir.dash&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;- running: C:\Users\s\AppData\Local\opam\.cygwin\root\bin\dash.exe &amp;quot;&#x2F;etc&#x2F;postinstall&#x2F;zp_man-db-update-index.dash&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;- Ending cygwin install
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt; Processing actions &amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;  🐫
&lt;&#x2F;span&gt;&lt;span&gt;⬇ retrieved mingw-w64-shims.0.2.0  (cached)
&lt;&#x2F;span&gt;&lt;span&gt;⊘ removed   mingw-w64-shims.0.2.0
&lt;&#x2F;span&gt;&lt;span&gt;∗ installed conf-pkg-config.4
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;#=== ERROR while compiling conf-portaudio.1 ===================================#
&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;pkg-config&amp;quot;: command not found.
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt; Error report &amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;  🐫
&lt;&#x2F;span&gt;&lt;span&gt;┌─ The following actions failed
&lt;&#x2F;span&gt;&lt;span&gt;│ λ build conf-portaudio 1
&lt;&#x2F;span&gt;&lt;span&gt;└─
&lt;&#x2F;span&gt;&lt;span&gt;┌─ The following changes have been performed (the rest was aborted)
&lt;&#x2F;span&gt;&lt;span&gt;│ ⊘ remove  mingw-w64-shims 0.2.0
&lt;&#x2F;span&gt;&lt;span&gt;│ ∗ install conf-pkg-config 4
&lt;&#x2F;span&gt;&lt;span&gt;└─
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;A few things have gone wrong here. Firstly, from:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;The following system packages will first need to be installed:
&lt;&#x2F;span&gt;&lt;span&gt;    pkgconf
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;…we can see that Opam has detected that some &lt;code&gt;depexts&lt;&#x2F;code&gt; need to be installed, but
only &lt;code&gt;pkgconf&lt;&#x2F;code&gt; and not &lt;code&gt;portaudio&lt;&#x2F;code&gt; are on the list. To help understand why, look
at the &lt;code&gt;depexts&lt;&#x2F;code&gt; of the &lt;code&gt;conf-pkg-config&lt;&#x2F;code&gt; package:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;PS C:\Users\s&amp;gt; opam show conf-pkg-config
&lt;&#x2F;span&gt;&lt;span&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;depexts     [&amp;quot;pkg-config&amp;quot;] {os-family = &amp;quot;debian&amp;quot; | os-family = &amp;quot;ubuntu&amp;quot;}
&lt;&#x2F;span&gt;&lt;span&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;            [&amp;quot;system:pkgconf&amp;quot;] {os = &amp;quot;win32&amp;quot; &amp;amp; os-distribution = &amp;quot;cygwinports&amp;quot;}
&lt;&#x2F;span&gt;&lt;span&gt;            [&amp;quot;pkgconf&amp;quot;] {os-distribution = &amp;quot;cygwin&amp;quot;}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This package makes an explicit distinction between “cygwinports” and “cygwin” so
it stands to reason that my initial concerns about &lt;code&gt;os-distribution = &quot;cygwinports&quot;&lt;&#x2F;code&gt; were merited.&lt;&#x2F;p&gt;
&lt;p&gt;The second issue is that even though Opam installed &lt;code&gt;pkgconf&lt;&#x2F;code&gt; with Cygwin, the
&lt;code&gt;pkg-config&lt;&#x2F;code&gt; executable couldn’t be found while installing the &lt;code&gt;conf-portaudio&lt;&#x2F;code&gt;
package. Remember earlier we located the Cygwin installation that Opam will be
managing? Looking in &lt;code&gt;C:\Users\s\AppData\Local\opam\.cygwin\root\bin&lt;&#x2F;code&gt;, there
is a program &lt;code&gt;pkgconf.exe&lt;&#x2F;code&gt;, but not a &lt;code&gt;pkg-config.exe&lt;&#x2F;code&gt;. At some
point in the past few years &lt;code&gt;pkg-config&lt;&#x2F;code&gt; was renamed to &lt;code&gt;pkgconf&lt;&#x2F;code&gt; and this change is slowly trickling
into different package managers at different rates.&lt;&#x2F;p&gt;
&lt;p&gt;The first thing to try here is modifying the package metadata for
&lt;code&gt;conf-portaudio&lt;&#x2F;code&gt; to use the correct name for &lt;code&gt;pkgconf&lt;&#x2F;code&gt;. Start by saving the
existing metadata to a local file which we can then modify:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;PS C:\Users\s&amp;gt; opam show conf-portaudio --raw &amp;gt; conf-portaudio.opam
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;We should now be able to install the &lt;code&gt;conf-portaudio&lt;&#x2F;code&gt; package from this file
rather than using the metadata stored in the Opam repo by running:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;PS C:\Users\s&amp;gt; opam install .\conf-portaudio.opam
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This command hung with &lt;code&gt;Processing 1&#x2F;1: [conf-portaudio.1: rsync]&lt;&#x2F;code&gt; so I left it
running while I went to run some errands and when I got back over an hour later
it was still hung.&lt;&#x2F;p&gt;
&lt;p&gt;I cancelled the hung operation. Despite not completing it did have the side
effect of creating an Opam “pin” for the &lt;code&gt;conf-portaudio&lt;&#x2F;code&gt; package.
An Opam pin is a configuration to override the source of a package, usually to
allow for local development of said package. We can see all the current Opam
pins with:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;PS C:\Users\s&amp;gt; opam pin list
&lt;&#x2F;span&gt;&lt;span&gt;conf-portaudio.1  (uninstalled)  rsync  file:&#x2F;&#x2F;C:&#x2F;Users&#x2F;s
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Before proceeding I wanted to remove the pin since something has clearly gone wrong:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;PS C:\Users\s&amp;gt; opam pin remove conf-portaudio
&lt;&#x2F;span&gt;&lt;span&gt;Cannot remove C:\Users\s\AppData\Local\opam\default\.opam-switch\sources\conf-portaudio\
&lt;&#x2F;span&gt;&lt;span&gt;AppData\Local\Application Data\Application Data\Application Data\Application Data\
&lt;&#x2F;span&gt;&lt;span&gt;Application Data\Application Data\Application Data\Application Data\Application Data\
&lt;&#x2F;span&gt;&lt;span&gt;Temporary Internet Files (C:\Users\s\AppData\Local\Microsoft\WinGet\Packages\
&lt;&#x2F;span&gt;&lt;span&gt;OCaml.opam_Microsoft.Winget.Source_8wekyb3d8bbwe\opam.exe: &amp;quot;unlink&amp;quot; failed on
&lt;&#x2F;span&gt;&lt;span&gt;C:\Users\s\AppData\Local\opam\default\.opam-switch\sources\conf-portaudio\AppData\
&lt;&#x2F;span&gt;&lt;span&gt;Local\Application Data\Application Data\Application Data\Application Data\
&lt;&#x2F;span&gt;&lt;span&gt;Application Data\Application Data\Application Data\Application Data\Application Data\
&lt;&#x2F;span&gt;&lt;span&gt;Temporary Internet Files: No such file or directory).
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Hmm. The &lt;code&gt;Application Data\Application Data\Application Data&lt;&#x2F;code&gt; component of the path looks suspicious.
Opam creates a copy of pinned packages so let’s take a look at &lt;code&gt;conf-portaudio&lt;&#x2F;code&gt;’s
copy in
&lt;code&gt;C:\Users\s\AppData\Local\opam\default\.opam-switch\sources\conf-portaudio&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;PS C:\Users\s&amp;gt; ls .\AppData\Local\opam\default\.opam-switch\sources\conf-portaudio\
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    Directory: C:\Users\s\AppData\Local\opam\default\.opam-switch\sources\conf-portaudio
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;Mode                 LastWriteTime         Length Name
&lt;&#x2F;span&gt;&lt;span&gt;----                 -------------         ------ ----
&lt;&#x2F;span&gt;&lt;span&gt;d----          04&#x2F;11&#x2F;2024    21:49                .bin
&lt;&#x2F;span&gt;&lt;span&gt;d----          04&#x2F;11&#x2F;2024    22:26                .cargo
&lt;&#x2F;span&gt;&lt;span&gt;d----          04&#x2F;11&#x2F;2024    21:49                .completions
&lt;&#x2F;span&gt;&lt;span&gt;d----          04&#x2F;11&#x2F;2024    21:49                .config
&lt;&#x2F;span&gt;&lt;span&gt;d----          22&#x2F;11&#x2F;2024    14:34                .dotfiles
&lt;&#x2F;span&gt;&lt;span&gt;d----          04&#x2F;11&#x2F;2024    21:49                .emacs.d
&lt;&#x2F;span&gt;&lt;span&gt;d----          25&#x2F;11&#x2F;2024    15:12                .local
&lt;&#x2F;span&gt;&lt;span&gt;d----          04&#x2F;11&#x2F;2024    21:59                .ms-ad
&lt;&#x2F;span&gt;&lt;span&gt;d----          04&#x2F;11&#x2F;2024    22:25                .rustup
&lt;&#x2F;span&gt;&lt;span&gt;d----          04&#x2F;11&#x2F;2024    21:29                .ssh
&lt;&#x2F;span&gt;&lt;span&gt;d----          18&#x2F;11&#x2F;2024    14:35                .vscode
&lt;&#x2F;span&gt;&lt;span&gt;d----          29&#x2F;05&#x2F;2025    16:03                AppData
&lt;&#x2F;span&gt;&lt;span&gt;-a---          28&#x2F;03&#x2F;2025    17:00           3654 .bash_history
&lt;&#x2F;span&gt;&lt;span&gt;-a---          25&#x2F;11&#x2F;2024    18:07           9168 .bashrc
&lt;&#x2F;span&gt;&lt;span&gt;...
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Hey, that looks like my home directory! It appears I unwittingly pinned the
&lt;code&gt;conf-portaudio&lt;&#x2F;code&gt; package to my entire home directory, and since Opam installs
packages &lt;em&gt;inside&lt;&#x2F;em&gt; my home directory it was recurring forever, continuously
copying my home directory inside itself. No wonder it hung.&lt;&#x2F;p&gt;
&lt;p&gt;Manually fixing the problem:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;PS C:\Users\s&amp;gt; rm .\AppData\Local\opam\default\.opam-switch\sources\conf-portaudio
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;Confirm
&lt;&#x2F;span&gt;&lt;span&gt;The item at C:\Users\s\AppData\Local\opam\default\.opam-switch\sources\conf-portaudio has children and the Recurse parameter was not specified. If you continue, all children will be removed with the item. Are you sure you want to continue?
&lt;&#x2F;span&gt;&lt;span&gt;[Y] Yes  [A] Yes to All  [N] No  [L] No to All  [S] Suspend  [?] Help (default is &amp;quot;Y&amp;quot;): A
&lt;&#x2F;span&gt;&lt;span&gt;Removed 32536 of 134845 files [1.197 GB of 15.926 GB (48.6 MB&#x2F;s)
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;That’s almost &lt;code&gt;16GB&lt;&#x2F;code&gt; of recursive copies of my home directory! This command
ended up failing due to permission problems so I had to delete it from the
explorer UI instead as I’m not very familiar with file management within
PowerShell.&lt;&#x2F;p&gt;
&lt;p&gt;Now that I manually deleted that folder, removing the pin works:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;PS C:\Users\s&amp;gt; opam pin remove conf-portaudio
&lt;&#x2F;span&gt;&lt;span&gt;Ok, conf-portaudio is no longer pinned to file:&#x2F;&#x2F;C:&#x2F;Users&#x2F;s (version 1)
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;What I’ve learnt here is that Opam doesn’t pin individual files but rather
entire directories. A pin is a mapping from package to directory, and when you
run &lt;code&gt;opam pin .\foo.opam&lt;&#x2F;code&gt; Opam learns the package name is &lt;code&gt;foo&lt;&#x2F;code&gt; from the name of
the file (I think), but assumes the directory is the directory containing
&lt;code&gt;foo.opam&lt;&#x2F;code&gt;. Most Opam package source directories contain an opam file and the
source code for the package. What’s unusual in this case is that the
&lt;code&gt;conf-portaudio&lt;&#x2F;code&gt; package does not have source code. We say it’s a “metapackage”
as installing it just installs its dependencies (including external
dependencies) and runs a command to verify their installation, but it has no
source code of its own. So in order to pin &lt;code&gt;conf-portaudio&lt;&#x2F;code&gt; we need to first
create a new directory and move the opam file there.&lt;&#x2F;p&gt;
&lt;p&gt;Pinning the directory is sufficient as Opam will scan the directory for any opam files
and create pins for each corresponding package using the current directory as the source.
I’ve also modified the opam file to run &lt;code&gt;pkgconf&lt;&#x2F;code&gt; rather than &lt;code&gt;pkg-config&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;PS C:\Users\s\conf-portaudio&amp;gt; opam pin .
&lt;&#x2F;span&gt;&lt;span&gt;[NOTE] Package conf-portaudio is already pinned to file:&#x2F;&#x2F;C:&#x2F;Users&#x2F;s&#x2F;conf-portaudio (version 1).
&lt;&#x2F;span&gt;&lt;span&gt;conf-portaudio is now pinned to file:&#x2F;&#x2F;C:&#x2F;Users&#x2F;s&#x2F;conf-portaudio (version 1)
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;The following actions will be performed:
&lt;&#x2F;span&gt;&lt;span&gt;=== install 1 package
&lt;&#x2F;span&gt;&lt;span&gt;  ∗ conf-portaudio 1 (pinned)
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;Proceed with ∗ 1 installation? [y&#x2F;n] n   (don&amp;#39;t want to install it just yet)
&lt;&#x2F;span&gt;&lt;span&gt;[NOTE] Pinning command successful, but your installed packages may be out of sync.
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;By default &lt;code&gt;opam pin&lt;&#x2F;code&gt; will prompt to install the pinned package in addition to just storing
the package → directory mapping, but I just want to create the mapping for now so I
chose &lt;code&gt;n&lt;&#x2F;code&gt; at the prompt. Before installing the package I want to make sure that
pinning it had the desired effect. Ask Opam what it thinks should be the package metadata
for &lt;code&gt;conf-portaudio&lt;&#x2F;code&gt; now:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;PS C:\Users\s\conf-portaudio&amp;gt; opam show conf-portaudio --raw
&lt;&#x2F;span&gt;&lt;span&gt;opam-version: &amp;quot;2.0&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;name: &amp;quot;conf-portaudio&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;version: &amp;quot;1&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;synopsis: &amp;quot;Virtual package relying on portaudio&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;description:
&lt;&#x2F;span&gt;&lt;span&gt;  &amp;quot;This package can only install if the portaudio library is installed on the system.&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;maintainer: &amp;quot;https:&#x2F;&#x2F;github.com&#x2F;ocaml&#x2F;opam-repository&#x2F;issues&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;authors: &amp;quot;portaudio dev team&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;license: &amp;quot;BSD-1-Clause&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;homepage: &amp;quot;http:&#x2F;&#x2F;www.portaudio.com&#x2F;&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;bug-reports: &amp;quot;https:&#x2F;&#x2F;github.com&#x2F;ocaml&#x2F;opam-repository&#x2F;issues&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;depends: [
&lt;&#x2F;span&gt;&lt;span&gt;  &amp;quot;conf-pkg-config&amp;quot; {build}
&lt;&#x2F;span&gt;&lt;span&gt;]
&lt;&#x2F;span&gt;&lt;span&gt;flags: conf
&lt;&#x2F;span&gt;&lt;span&gt;build: [&amp;quot;pkgconf&amp;quot; &amp;quot;--exists&amp;quot; &amp;quot;portaudio-2.0&amp;quot;]
&lt;&#x2F;span&gt;&lt;span&gt;depexts: [
&lt;&#x2F;span&gt;&lt;span&gt;  [&amp;quot;portaudio-dev&amp;quot;] {os-distribution = &amp;quot;alpine&amp;quot;}
&lt;&#x2F;span&gt;&lt;span&gt;  [&amp;quot;portaudio-devel&amp;quot;]
&lt;&#x2F;span&gt;&lt;span&gt;    {os-distribution = &amp;quot;centos&amp;quot; | os-family = &amp;quot;fedora&amp;quot; | os-family = &amp;quot;suse&amp;quot; |
&lt;&#x2F;span&gt;&lt;span&gt;     os-family = &amp;quot;opensuse&amp;quot;}
&lt;&#x2F;span&gt;&lt;span&gt;  [&amp;quot;portaudio&amp;quot;] {os = &amp;quot;macos&amp;quot; &amp;amp; os-distribution = &amp;quot;homebrew&amp;quot;}
&lt;&#x2F;span&gt;&lt;span&gt;  [&amp;quot;portaudio&amp;quot;]
&lt;&#x2F;span&gt;&lt;span&gt;    {os = &amp;quot;freebsd&amp;quot; | os-family = &amp;quot;arch&amp;quot; | os-distribution = &amp;quot;nixos&amp;quot; |
&lt;&#x2F;span&gt;&lt;span&gt;     os-distribution = &amp;quot;ol&amp;quot;}
&lt;&#x2F;span&gt;&lt;span&gt;  [&amp;quot;portaudio&amp;quot;] {os = &amp;quot;win32&amp;quot; &amp;amp; os-distribution = &amp;quot;cygwinports&amp;quot;}
&lt;&#x2F;span&gt;&lt;span&gt;  [&amp;quot;portaudio19-dev&amp;quot;] {os-family = &amp;quot;debian&amp;quot; | os-family = &amp;quot;ubuntu&amp;quot;}
&lt;&#x2F;span&gt;&lt;span&gt;]
&lt;&#x2F;span&gt;&lt;span&gt;url {
&lt;&#x2F;span&gt;&lt;span&gt;  src: &amp;quot;file:&#x2F;&#x2F;C:&#x2F;Users&#x2F;s&#x2F;conf-portaudio&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Note that the &lt;code&gt;build&lt;&#x2F;code&gt; command calls &lt;code&gt;pkgconf&lt;&#x2F;code&gt; rather than &lt;code&gt;pkg-config&lt;&#x2F;code&gt; as it did
previously, indicating that my custom version of &lt;code&gt;conf-portaudio&lt;&#x2F;code&gt; is being used.&lt;&#x2F;p&gt;
&lt;p&gt;Now we can try installing it again.&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;PS C:\Users\s\conf-portaudio&amp;gt; opam install conf-portaudio
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt; Synchronising pinned packages &amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;  🐫
&lt;&#x2F;span&gt;&lt;span&gt;[conf-portaudio.1] synchronised (no changes)
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;The following actions will be performed:
&lt;&#x2F;span&gt;&lt;span&gt;=== install 1 package
&lt;&#x2F;span&gt;&lt;span&gt;  ∗ conf-portaudio 1 (pinned)
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt; Processing actions &amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;  🐫
&lt;&#x2F;span&gt;&lt;span&gt;[ERROR] The compilation of conf-portaudio.1 failed at &amp;quot;pkgconf --exists portaudio-2.0&amp;quot;.
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;#=== ERROR while compiling conf-portaudio.1 ===================================#
&lt;&#x2F;span&gt;&lt;span&gt;# context     2.3.0 | win32&#x2F;x86_64 | ocaml.5.3.0 | pinned(file:&#x2F;&#x2F;C:&#x2F;Users&#x2F;s&#x2F;conf-portaudio)
&lt;&#x2F;span&gt;&lt;span&gt;# path        ~\AppData\Local\opam\default\.opam-switch\build\conf-portaudio.1
&lt;&#x2F;span&gt;&lt;span&gt;# command     ~\AppData\Local\opam\.cygwin\root\bin\pkgconf.exe --exists portaudio-2.0
&lt;&#x2F;span&gt;&lt;span&gt;# exit-code   1
&lt;&#x2F;span&gt;&lt;span&gt;# env-file    ~\AppData\Local\opam\log\conf-portaudio-12212-533065.env
&lt;&#x2F;span&gt;&lt;span&gt;# output-file ~\AppData\Local\opam\log\conf-portaudio-12212-533065.out
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt; Error report &amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;  🐫
&lt;&#x2F;span&gt;&lt;span&gt;┌─ The following actions failed
&lt;&#x2F;span&gt;&lt;span&gt;│ λ build conf-portaudio 1
&lt;&#x2F;span&gt;&lt;span&gt;└─
&lt;&#x2F;span&gt;&lt;span&gt;╶─ No changes have been performed
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;That’s progress! There’s now an error message indicating that &lt;code&gt;pkgconf --exists portaudio-2.0&lt;&#x2F;code&gt; failed, which is expected because the &lt;code&gt;conf-portaudio&lt;&#x2F;code&gt; package’s
&lt;code&gt;depexts&lt;&#x2F;code&gt; don’t contain a case for &lt;code&gt;os-distribution = &quot;cygwin&quot;&lt;&#x2F;code&gt; (only &lt;code&gt;os-distribution = &quot;cygwinports&quot;&lt;&#x2F;code&gt;).&lt;&#x2F;p&gt;
&lt;p&gt;Next step is to fix the &lt;code&gt;conf-portaudio&lt;&#x2F;code&gt; &lt;code&gt;depexts&lt;&#x2F;code&gt; so they install the appropriate
package on Cygwin. I added this line to the &lt;code&gt;depexts&lt;&#x2F;code&gt; section in &lt;code&gt;conf-portaudio.opam&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;    [&amp;quot;portaudio&amp;quot;] {os = &amp;quot;win32&amp;quot; &amp;amp; os-distribution = &amp;quot;cygwin&amp;quot;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;I needed to run &lt;code&gt;opam pin .&lt;&#x2F;code&gt; again to get Opam to update its copy of the custom
version of this package’s metadata. Every time you change the opam file of a
pinned package you have to re-pin the package for the change to be come visible
to Opam.&lt;&#x2F;p&gt;
&lt;p&gt;Then I tried installing &lt;code&gt;conf-portaudio&lt;&#x2F;code&gt; again:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;PS C:\Users\s\conf-portaudio&amp;gt; opam install conf-portaudio
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt; Synchronising pinned packages &amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;  🐫
&lt;&#x2F;span&gt;&lt;span&gt;[conf-portaudio.1] synchronised (no changes)
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;The following actions will be performed:
&lt;&#x2F;span&gt;&lt;span&gt;=== install 1 package
&lt;&#x2F;span&gt;&lt;span&gt;  ∗ conf-portaudio 1 (pinned)
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;The following system packages will first need to be installed:
&lt;&#x2F;span&gt;&lt;span&gt;    portaudio
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt; Handling external dependencies &amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;  🐫
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;+ C:\Users\s\AppData\Local\opam\.cygwin\setup-x86_64.exe &amp;quot;--root&amp;quot; &amp;quot;C:\\Users\\s\\AppData\\Local\\opam\\.cygwin\\root&amp;quot; &amp;quot;--quiet-mode&amp;quot; &amp;quot;noinput&amp;quot; &amp;quot;--no-shortcuts&amp;quot; &amp;quot;--no-startmenu&amp;quot; &amp;quot;--no-desktop&amp;quot; &amp;quot;--no-admin&amp;quot; &amp;quot;--no-version-check&amp;quot; &amp;quot;--no-write-registry&amp;quot; &amp;quot;--packages&amp;quot; &amp;quot;portaudio&amp;quot; &amp;quot;--upgrade-also&amp;quot; &amp;quot;--only-site&amp;quot; &amp;quot;--site&amp;quot; &amp;quot;https:&#x2F;&#x2F;cygwin.mirror.constant.com&#x2F;&amp;quot; &amp;quot;--local-package-dir&amp;quot; &amp;quot;C:\\Users\\s\\AppData\\Local\\opam\\.cygwin\\cache&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;- Starting cygwin install, version 2.934
&lt;&#x2F;span&gt;&lt;span&gt;- User has NO backup&#x2F;restore rights
&lt;&#x2F;span&gt;&lt;span&gt;- User has NO symlink creation right
&lt;&#x2F;span&gt;&lt;span&gt;- io_stream_cygfile: fopen(&#x2F;etc&#x2F;setup&#x2F;setup.rc) failed 2 No such file or directory
&lt;&#x2F;span&gt;&lt;span&gt;- Current Directory: C:\Users\s\AppData\Local\opam\.cygwin\cache
&lt;&#x2F;span&gt;&lt;span&gt;- root: C:\Users\s\AppData\Local\opam\.cygwin\root user
&lt;&#x2F;span&gt;&lt;span&gt;- Changing gid back to original
&lt;&#x2F;span&gt;&lt;span&gt;- Selected local directory: C:\Users\s\AppData\Local\opam\.cygwin\cache
&lt;&#x2F;span&gt;&lt;span&gt;- net: Preconfig
&lt;&#x2F;span&gt;&lt;span&gt;- site: https:&#x2F;&#x2F;cygwin.mirror.constant.com&#x2F;
&lt;&#x2F;span&gt;&lt;span&gt;- Package &amp;#39;portaudio&amp;#39; not found.
&lt;&#x2F;span&gt;&lt;span&gt;- solving: 0 tasks, update: yes, use test packages: no
&lt;&#x2F;span&gt;&lt;span&gt;- solving: 0 tasks, update: no, use test packages: no
&lt;&#x2F;span&gt;&lt;span&gt;- Augmented Transaction List: is empty
&lt;&#x2F;span&gt;&lt;span&gt;- running: C:\Users\s\AppData\Local\opam\.cygwin\root\bin\dash.exe &amp;quot;&#x2F;etc&#x2F;postinstall&#x2F;0p_000_autorebase.dash&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;- running: C:\Users\s\AppData\Local\opam\.cygwin\root\bin\dash.exe &amp;quot;&#x2F;etc&#x2F;postinstall&#x2F;0p_update-info-dir.dash&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;- running: C:\Users\s\AppData\Local\opam\.cygwin\root\bin\dash.exe &amp;quot;&#x2F;etc&#x2F;postinstall&#x2F;zp_man-db-update-index.dash&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;- Ending cygwin install
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt; Processing actions &amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;  🐫
&lt;&#x2F;span&gt;&lt;span&gt;[ERROR] The compilation of conf-portaudio.1 failed at &amp;quot;pkgconf --exists portaudio-2.0&amp;quot;.
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;#=== ERROR while compiling conf-portaudio.1 ===================================#
&lt;&#x2F;span&gt;&lt;span&gt;# context     2.3.0 | win32&#x2F;x86_64 | ocaml.5.3.0 | pinned(file:&#x2F;&#x2F;C:&#x2F;Users&#x2F;s&#x2F;conf-portaudio)
&lt;&#x2F;span&gt;&lt;span&gt;# path        ~\AppData\Local\opam\default\.opam-switch\build\conf-portaudio.1
&lt;&#x2F;span&gt;&lt;span&gt;# command     ~\AppData\Local\opam\.cygwin\root\bin\pkgconf.exe --exists portaudio-2.0
&lt;&#x2F;span&gt;&lt;span&gt;# exit-code   1
&lt;&#x2F;span&gt;&lt;span&gt;# env-file    ~\AppData\Local\opam\log\conf-portaudio-9396-6b5019.env
&lt;&#x2F;span&gt;&lt;span&gt;# output-file ~\AppData\Local\opam\log\conf-portaudio-9396-6b5019.out
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt; Error report &amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;  🐫
&lt;&#x2F;span&gt;&lt;span&gt;┌─ The following actions failed
&lt;&#x2F;span&gt;&lt;span&gt;│ λ build conf-portaudio 1
&lt;&#x2F;span&gt;&lt;span&gt;└─
&lt;&#x2F;span&gt;&lt;span&gt;╶─ No changes have been performed
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This time Opam tried to install the &lt;code&gt;portaudio&lt;&#x2F;code&gt; Cygwin package, however &lt;code&gt;Package &#x27;portaudio&#x27; not found.&lt;&#x2F;code&gt; tells us that there is no such Cygwin package.&lt;&#x2F;p&gt;
&lt;p&gt;However digging around in the Cygwin package repo I did come across a package
named &lt;code&gt;mingw64-x86_64-portaudio&lt;&#x2F;code&gt;, though it’s unmaintained and hasn’t been
updated since 2018. I &lt;em&gt;also&lt;&#x2F;em&gt; happened to find the package
&lt;code&gt;mingw64-x86_64-libao&lt;&#x2F;code&gt;, also unmaintained and not updated since 2013, however if
this package works then I might be able to use &lt;code&gt;llama&lt;&#x2F;code&gt; on Windows unmodified,
since it’s currently based on libao.&lt;&#x2F;p&gt;
&lt;p&gt;Repeating the steps above to create a custom version of the &lt;code&gt;conf-ao&lt;&#x2F;code&gt; package -
Opam’s meta package for installing the system-appropriate libao package:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;PS C:\Users\s\conf-ao&amp;gt; opam show conf-ao --raw &amp;gt; conf-ao.opam
&lt;&#x2F;span&gt;&lt;span&gt;PS C:\Users\s\conf-ao&amp;gt; opam pin .
&lt;&#x2F;span&gt;&lt;span&gt;conf-ao is now pinned to file:&#x2F;&#x2F;C:&#x2F;Users&#x2F;s&#x2F;conf-ao (version 1)
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;The following actions will be performed:
&lt;&#x2F;span&gt;&lt;span&gt;=== install 1 package
&lt;&#x2F;span&gt;&lt;span&gt;  ∗ conf-ao 1 (pinned)
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;Proceed with ∗ 1 installation? [y&#x2F;n] n
&lt;&#x2F;span&gt;&lt;span&gt;[NOTE] Pinning command successful, but your installed packages may be out of sync.
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Then I modified &lt;code&gt;conf-ao.opam&lt;&#x2F;code&gt;, adding the following to its &lt;code&gt;depexts&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;  [&amp;quot;mingw64-x86_64-libao&amp;quot;] {os = &amp;quot;win32&amp;quot; &amp;amp; os-distribution = &amp;quot;cygwin&amp;quot;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;…and changed its build command to test for the system libao package using
&lt;code&gt;pkgconf&lt;&#x2F;code&gt; instead of &lt;code&gt;pkg-config&lt;&#x2F;code&gt;, the same as what I did for &lt;code&gt;conf-portaudio&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Moment of truth:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;PS C:\Users\s\conf-ao&amp;gt; opam install conf-ao
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt; Synchronising pinned packages &amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;  🐫
&lt;&#x2F;span&gt;&lt;span&gt;[conf-ao.1] synchronised (no changes)
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;The following actions will be performed:
&lt;&#x2F;span&gt;&lt;span&gt;=== install 1 package
&lt;&#x2F;span&gt;&lt;span&gt;  ∗ conf-ao 1 (pinned)
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;The following system packages will first need to be installed:
&lt;&#x2F;span&gt;&lt;span&gt;    mingw64-x86_64-libao
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt; Handling external dependencies &amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;  🐫
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;+ C:\Users\s\AppData\Local\opam\.cygwin\setup-x86_64.exe &amp;quot;--root&amp;quot; &amp;quot;C:\\Users\\s\\AppData\\Local\\opam\\.cygwin\\root&amp;quot; &amp;quot;--quiet-mode&amp;quot; &amp;quot;noinput&amp;quot; &amp;quot;--no-shortcuts&amp;quot; &amp;quot;--no-startmenu&amp;quot; &amp;quot;--no-desktop&amp;quot; &amp;quot;--no-admin&amp;quot; &amp;quot;--no-version-check&amp;quot; &amp;quot;--no-write-registry&amp;quot; &amp;quot;--packages&amp;quot; &amp;quot;mingw64-x86_64-libao&amp;quot; &amp;quot;--upgrade-also&amp;quot; &amp;quot;--only-site&amp;quot; &amp;quot;--site&amp;quot; &amp;quot;https:&#x2F;&#x2F;cygwin.mirror.constant.com&#x2F;&amp;quot; &amp;quot;--local-package-dir&amp;quot; &amp;quot;C:\\Users\\s\\AppData\\Local\\opam\\.cygwin\\cache&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;- Starting cygwin install, version 2.934
&lt;&#x2F;span&gt;&lt;span&gt;- User has NO backup&#x2F;restore rights
&lt;&#x2F;span&gt;&lt;span&gt;- User has NO symlink creation right
&lt;&#x2F;span&gt;&lt;span&gt;- Current Directory: C:\Users\s\AppData\Local\opam\.cygwin\cache
&lt;&#x2F;span&gt;&lt;span&gt;- root: C:\Users\s\AppData\Local\opam\.cygwin\root user
&lt;&#x2F;span&gt;&lt;span&gt;- Changing gid back to original
&lt;&#x2F;span&gt;&lt;span&gt;- Selected local directory: C:\Users\s\AppData\Local\opam\.cygwin\cache
&lt;&#x2F;span&gt;&lt;span&gt;- net: Preconfig
&lt;&#x2F;span&gt;&lt;span&gt;- site: https:&#x2F;&#x2F;cygwin.mirror.constant.com&#x2F;
&lt;&#x2F;span&gt;&lt;span&gt;- solving: 1 tasks, update: yes, use test packages: no
&lt;&#x2F;span&gt;&lt;span&gt;- solving: 1 tasks, update: no, use test packages: no
&lt;&#x2F;span&gt;&lt;span&gt;- Augmented Transaction List:
&lt;&#x2F;span&gt;&lt;span&gt;-    0 install mingw64-x86_64-libao 1.1.0-1
&lt;&#x2F;span&gt;&lt;span&gt;- Downloaded C:\Users\s\AppData\Local\opam\.cygwin\cache&#x2F;https%3a%2f%2fcygwin.mirror.constant.com%2f&#x2F;noarch&#x2F;release&#x2F;mingw64-x86_64-libao&#x2F;mingw64-x86_64-libao-1.1.0-1.tar.xz
&lt;&#x2F;span&gt;&lt;span&gt;- Extracting from file:&#x2F;&#x2F;C:\Users\s\AppData\Local\opam\.cygwin\cache&#x2F;https%3a%2f%2fcygwin.mirror.constant.com%2f&#x2F;noarch&#x2F;release&#x2F;mingw64-x86_64-libao&#x2F;mingw64-x86_64-libao-1.1.0-1.tar.xz
&lt;&#x2F;span&gt;&lt;span&gt;- running: C:\Users\s\AppData\Local\opam\.cygwin\root\bin\dash.exe &amp;quot;&#x2F;etc&#x2F;postinstall&#x2F;0p_000_autorebase.dash&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;- running: C:\Users\s\AppData\Local\opam\.cygwin\root\bin\dash.exe &amp;quot;&#x2F;etc&#x2F;postinstall&#x2F;0p_update-info-dir.dash&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;- running: C:\Users\s\AppData\Local\opam\.cygwin\root\bin\dash.exe &amp;quot;&#x2F;etc&#x2F;postinstall&#x2F;zp_man-db-update-index.dash&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;- Ending cygwin install
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt; Processing actions &amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;  🐫
&lt;&#x2F;span&gt;&lt;span&gt;[ERROR] The compilation of conf-ao.1 failed at &amp;quot;pkgconf --exists ao&amp;quot;.
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;#=== ERROR while compiling conf-ao.1 ==========================================#
&lt;&#x2F;span&gt;&lt;span&gt;# context     2.3.0 | win32&#x2F;x86_64 | ocaml.5.3.0 | pinned(file:&#x2F;&#x2F;C:&#x2F;Users&#x2F;s&#x2F;conf-ao)
&lt;&#x2F;span&gt;&lt;span&gt;# path        ~\AppData\Local\opam\default\.opam-switch\build\conf-ao.1
&lt;&#x2F;span&gt;&lt;span&gt;# command     ~\AppData\Local\opam\.cygwin\root\bin\pkgconf.exe --exists ao
&lt;&#x2F;span&gt;&lt;span&gt;# exit-code   1
&lt;&#x2F;span&gt;&lt;span&gt;# env-file    ~\AppData\Local\opam\log\conf-ao-8080-676a1b.env
&lt;&#x2F;span&gt;&lt;span&gt;# output-file ~\AppData\Local\opam\log\conf-ao-8080-676a1b.out
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt; Error report &amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;  🐫
&lt;&#x2F;span&gt;&lt;span&gt;┌─ The following actions failed
&lt;&#x2F;span&gt;&lt;span&gt;│ λ build conf-ao 1
&lt;&#x2F;span&gt;&lt;span&gt;└─
&lt;&#x2F;span&gt;&lt;span&gt;╶─ No changes have been performed
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Ok so it found the &lt;code&gt;mingw64-x86_64-libao&lt;&#x2F;code&gt; package in the Cygwin repo but it
looks like &lt;code&gt;pkgconf&lt;&#x2F;code&gt; can’t see it.&lt;&#x2F;p&gt;
&lt;p&gt;Manually inspecting the Cygwin directory, and I do see that libao has been
installed:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;PS C:\Users\s\conf-ao&amp;gt; ls C:\Users\s\AppData\Local\opam\.cygwin\root\usr\x86_64-w64-mingw32\sys-root\mingw\lib\libao.*
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    Directory: C:\Users\s\AppData\Local\opam\.cygwin\root\usr\x86_64-w64-mingw32\sys-root\mingw\lib
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;Mode                 LastWriteTime         Length Name
&lt;&#x2F;span&gt;&lt;span&gt;----                 -------------         ------ ----
&lt;&#x2F;span&gt;&lt;span&gt;-a---          02&#x2F;12&#x2F;2013    15:27          50102 libao.a
&lt;&#x2F;span&gt;&lt;span&gt;-a---          02&#x2F;12&#x2F;2013    15:27           9386 libao.dll.a
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;But that’s doesn’t look like a standard path for libraries.
Reading more about the &lt;code&gt;mingw64-x86_64-*&lt;&#x2F;code&gt; packages in the Cygwin repo and it
doesn’t look like I’ll easily be able to mix them with other Cygwin packages,
which could make it tricky to proceed with Opam in its current configuration
where it manages a Cygwin environment for us.&lt;&#x2F;p&gt;
&lt;p&gt;An alternative package manager for Windows is “Msys2”. During &lt;code&gt;opam init&lt;&#x2F;code&gt; you
can select Msys2 as an alternative to Cygwin, though there’s no option for Opam
to install the Msys2 environment for us. There appears to be a &lt;code&gt;libao&lt;&#x2F;code&gt; package in
the Msys2 repository, so the next step will be to try setting up an Msys2
environment and a new Opam installation which uses it and then install the
&lt;code&gt;conf-ao&lt;&#x2F;code&gt; package along with its &lt;code&gt;depexts&lt;&#x2F;code&gt; within that environment.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;trying-again-with-msys2&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#trying-again-with-msys2&quot; aria-label=&quot;Anchor link for: trying-again-with-msys2&quot;&gt;Trying again with Msys2&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;Firstly I deleted &lt;code&gt;C:\Users\s\AppData\Local\opam&lt;&#x2F;code&gt; to get a fresh start.&lt;&#x2F;p&gt;
&lt;p&gt;I installed Msys2 from the installer at &lt;a href=&quot;https:&#x2F;&#x2F;www.msys2.org&#x2F;&quot;&gt;its website&lt;&#x2F;a&gt;,
which created a directory &lt;code&gt;C:\msys64&lt;&#x2F;code&gt; which will be the root directory of our
Msys2 environment.&lt;&#x2F;p&gt;
&lt;p&gt;Then I reran &lt;code&gt;opam init&lt;&#x2F;code&gt; to create a new Opam environment, this time selecting
the second option for obtaining Unix tools: &lt;code&gt;Use an existing Cygwin&#x2F;MSYS2 installation&lt;&#x2F;code&gt;. I’ll post the entire output of the interactive session with &lt;code&gt;opam init&lt;&#x2F;code&gt; in case it helps someone set up Opam on their machine:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;PS C:\Users\s&amp;gt; opam init
&lt;&#x2F;span&gt;&lt;span&gt;No configuration file found, using built-in defaults.
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt; Windows Developer Mode &amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;  🐫
&lt;&#x2F;span&gt;&lt;span&gt;opam does not require Developer Mode to be enabled on Windows, but it is
&lt;&#x2F;span&gt;&lt;span&gt;recommended, in particular because it enables support for symlinks without
&lt;&#x2F;span&gt;&lt;span&gt;requiring opam to be run elevated (which we do not recommend doing).
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;More information on enabling Developer Mode may be obtained from
&lt;&#x2F;span&gt;&lt;span&gt;https:&#x2F;&#x2F;learn.microsoft.com&#x2F;en-gb&#x2F;windows&#x2F;apps&#x2F;get-started&#x2F;enable-your-device-for-development
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt; Unix support infrastructure &amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;  🐫
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;opam and the OCaml ecosystem in general require various Unix tools in order to operate correctly. At present, this requires the installation of Cygwin to provide these tools.
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;How should opam obtain Unix tools?
&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt; 1. Automatically create an internal Cygwin installation that will be managed by opam (recommended)
&lt;&#x2F;span&gt;&lt;span&gt;  2. Use an existing Cygwin&#x2F;MSYS2 installation
&lt;&#x2F;span&gt;&lt;span&gt;  3. Abort initialisation
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;[1&#x2F;2&#x2F;3] 2
&lt;&#x2F;span&gt;&lt;span&gt;Enter the prefix of an existing Cygwin installation (e.g. C:\cygwin64) C:\msys64
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;The following system packages will first need to be installed:
&lt;&#x2F;span&gt;&lt;span&gt;    diffutils make patch rsync unzip
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt; Handling external dependencies &amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;  🐫
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;opam believes some required external dependencies are missing. opam can:
&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt; 1. Run C:\msys64\usr\bin\pacman.exe to install them (may need root&#x2F;sudo access)
&lt;&#x2F;span&gt;&lt;span&gt;  2. Display the recommended C:\msys64\usr\bin\pacman.exe command and wait while you run it manually (e.g. in another terminal)
&lt;&#x2F;span&gt;&lt;span&gt;  3. Continue anyway, and, upon success, permanently register that this external dependency is present, but not detectable
&lt;&#x2F;span&gt;&lt;span&gt;  4. Abort the installation
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;[1&#x2F;2&#x2F;3&#x2F;4] 1
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;+ C:\msys64\usr\bin\pacman.exe &amp;quot;-Su&amp;quot; &amp;quot;--noconfirm&amp;quot; &amp;quot;diffutils&amp;quot; &amp;quot;make&amp;quot; &amp;quot;patch&amp;quot; &amp;quot;rsync&amp;quot; &amp;quot;unzip&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;- :: Starting core system upgrade...
&lt;&#x2F;span&gt;&lt;span&gt;-  there is nothing to do
&lt;&#x2F;span&gt;&lt;span&gt;- :: Starting full system upgrade...
&lt;&#x2F;span&gt;&lt;span&gt;- resolving dependencies...
&lt;&#x2F;span&gt;&lt;span&gt;- looking for conflicting packages...
&lt;&#x2F;span&gt;&lt;span&gt;-
&lt;&#x2F;span&gt;&lt;span&gt;- Packages (6) libxxhash-0.8.3-1  diffutils-3.10-1  make-4.4.1-2  patch-2.7.6-3  rsync-3.4.1-1  unzip-6.0-3
&lt;&#x2F;span&gt;&lt;span&gt;-
&lt;&#x2F;span&gt;&lt;span&gt;- Total Download Size:   1.43 MiB
&lt;&#x2F;span&gt;&lt;span&gt;- Total Installed Size:  4.51 MiB
&lt;&#x2F;span&gt;&lt;span&gt;-
&lt;&#x2F;span&gt;&lt;span&gt;- :: Proceed with installation? [Y&#x2F;n]
&lt;&#x2F;span&gt;&lt;span&gt;- :: Retrieving packages...
&lt;&#x2F;span&gt;&lt;span&gt;-  make-4.4.1-2-x86_64 downloading...
&lt;&#x2F;span&gt;&lt;span&gt;-  diffutils-3.10-1-x86_64 downloading...
&lt;&#x2F;span&gt;&lt;span&gt;-  rsync-3.4.1-1-x86_64 downloading...
&lt;&#x2F;span&gt;&lt;span&gt;-  unzip-6.0-3-x86_64 downloading...
&lt;&#x2F;span&gt;&lt;span&gt;-  patch-2.7.6-3-x86_64 downloading...
&lt;&#x2F;span&gt;&lt;span&gt;-  libxxhash-0.8.3-1-x86_64 downloading...
&lt;&#x2F;span&gt;&lt;span&gt;- checking keyring...
&lt;&#x2F;span&gt;&lt;span&gt;- checking package integrity...
&lt;&#x2F;span&gt;&lt;span&gt;- loading package files...
&lt;&#x2F;span&gt;&lt;span&gt;- checking for file conflicts...
&lt;&#x2F;span&gt;&lt;span&gt;- checking available disk space...
&lt;&#x2F;span&gt;&lt;span&gt;- :: Processing package changes...
&lt;&#x2F;span&gt;&lt;span&gt;- installing diffutils...
&lt;&#x2F;span&gt;&lt;span&gt;- installing make...
&lt;&#x2F;span&gt;&lt;span&gt;- installing patch...
&lt;&#x2F;span&gt;&lt;span&gt;- Optional dependencies for patch
&lt;&#x2F;span&gt;&lt;span&gt;-     ed: for patch -e functionality
&lt;&#x2F;span&gt;&lt;span&gt;- installing libxxhash...
&lt;&#x2F;span&gt;&lt;span&gt;- installing rsync...
&lt;&#x2F;span&gt;&lt;span&gt;- installing unzip...
&lt;&#x2F;span&gt;&lt;span&gt;- :: Running post-transaction hooks...
&lt;&#x2F;span&gt;&lt;span&gt;- (1&#x2F;1) Updating the info directory file...
&lt;&#x2F;span&gt;&lt;span&gt;Checking for available remotes: rsync and local, git.
&lt;&#x2F;span&gt;&lt;span&gt;  - you won&amp;#39;t be able to use mercurial repositories unless you install the hg command on your system.
&lt;&#x2F;span&gt;&lt;span&gt;  - you won&amp;#39;t be able to use darcs repositories unless you install the darcs command on your system.
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt; Fetching repository information &amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;  🐫
&lt;&#x2F;span&gt;&lt;span&gt;[default] Initialised
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt; Required setup - please read &amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;  🐫
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;  In normal operation, opam only alters files within ~\AppData\Local\opam.
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;  However, to best integrate with your system, some environment variables
&lt;&#x2F;span&gt;&lt;span&gt;  should be set. When you want to access your opam installation, you will
&lt;&#x2F;span&gt;&lt;span&gt;  need to run:
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    (&amp;amp; opam env) -split &amp;#39;\r?\n&amp;#39; | ForEach-Object { Invoke-Expression $_ }
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;  You can always re-run this setup with &amp;#39;opam init&amp;#39; later.
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;opam doesn&amp;#39;t have any configuration options for pwsh; you will have to run (&amp;amp; opam env) -split &amp;#39;\r?\n&amp;#39; | ForEach-Object { Invoke-Expression $_ } whenever you change you current &amp;#39;opam switch&amp;#39; or start a new terminal session. Alternatively, would you like to
&lt;&#x2F;span&gt;&lt;span&gt;select a different shell? [y&#x2F;n] n
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt; Creating initial switch &amp;#39;default&amp;#39; (invariant [&amp;quot;ocaml&amp;quot; {&amp;gt;= &amp;quot;4.05.0&amp;quot;}] - initially with ocaml-base-compiler)
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt; Installing new switch packages &amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;  🐫
&lt;&#x2F;span&gt;&lt;span&gt;Switch invariant: [&amp;quot;ocaml&amp;quot; {&amp;gt;= &amp;quot;4.05.0&amp;quot;}]
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;The following system packages will first need to be installed:
&lt;&#x2F;span&gt;&lt;span&gt;    mingw-w64-x86_64-gcc
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt; Handling external dependencies &amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;  🐫
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;opam believes some required external dependencies are missing. opam can:
&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt; 1. Run C:\msys64\usr\bin\pacman.exe to install them (may need root&#x2F;sudo access)
&lt;&#x2F;span&gt;&lt;span&gt;  2. Display the recommended C:\msys64\usr\bin\pacman.exe command and wait while you run it manually (e.g. in another terminal)
&lt;&#x2F;span&gt;&lt;span&gt;  3. Continue anyway, and, upon success, permanently register that this external dependency is present, but not detectable
&lt;&#x2F;span&gt;&lt;span&gt;  4. Abort the installation
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;[1&#x2F;2&#x2F;3&#x2F;4] 1
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;+ C:\msys64\usr\bin\pacman.exe &amp;quot;-Su&amp;quot; &amp;quot;--noconfirm&amp;quot; &amp;quot;mingw-w64-x86_64-gcc&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;- :: Starting core system upgrade...
&lt;&#x2F;span&gt;&lt;span&gt;-  there is nothing to do
&lt;&#x2F;span&gt;&lt;span&gt;- :: Starting full system upgrade...
&lt;&#x2F;span&gt;&lt;span&gt;- resolving dependencies...
&lt;&#x2F;span&gt;&lt;span&gt;- looking for conflicting packages...
&lt;&#x2F;span&gt;&lt;span&gt;-
&lt;&#x2F;span&gt;&lt;span&gt;- Packages (16) mingw-w64-x86_64-binutils-2.44-1  mingw-w64-x86_64-crt-git-12.0.0.r509.g079e6092b-1  mingw-w64-x86_64-gcc-libs-14.2.0-2  mingw-w64-x86_64-gettext-runtime-0.23.1-1  mingw-w64-x86_64-gmp-6.3.0-2  mingw-w64-x86_64-headers-git-12.0.0.r509.g079e6092b-1  mingw-w64-x86_64-isl-0.27-1  mingw-w64-x86_64-libiconv-1.18-1  mingw-w64-x86_64-libwinpthread-git-12.0.0.r509.g079e6092b-1  mingw-w64-x86_64-mpc-1.3.1-2  mingw-w64-x86_64-mpfr-4.2.1-2  mingw-w64-x86_64-windows-default-manifest-6.4-4  mingw-w64-x86_64-winpthreads-git-12.0.0.r509.g079e6092b-1  mingw-w64-x86_64-zlib-1.3.1-1  mingw-w64-x86_64-zstd-1.5.7-1  mingw-w64-x86_64-gcc-14.2.0-2
&lt;&#x2F;span&gt;&lt;span&gt;-
&lt;&#x2F;span&gt;&lt;span&gt;- Total Download Size:    65.78 MiB
&lt;&#x2F;span&gt;&lt;span&gt;- Total Installed Size:  518.69 MiB
&lt;&#x2F;span&gt;&lt;span&gt;-
&lt;&#x2F;span&gt;&lt;span&gt;- :: Proceed with installation? [Y&#x2F;n]
&lt;&#x2F;span&gt;&lt;span&gt;- :: Retrieving packages...
&lt;&#x2F;span&gt;&lt;span&gt;-  mingw-w64-x86_64-gcc-14.2.0-2-any downloading...
&lt;&#x2F;span&gt;&lt;span&gt;-  mingw-w64-x86_64-headers-git-12.0.0.r509.g079e6092b-1-any downloading...
&lt;&#x2F;span&gt;&lt;span&gt;-  mingw-w64-x86_64-binutils-2.44-1-any downloading...
&lt;&#x2F;span&gt;&lt;span&gt;-  mingw-w64-x86_64-crt-git-12.0.0.r509.g079e6092b-1-any downloading...
&lt;&#x2F;span&gt;&lt;span&gt;-  mingw-w64-x86_64-isl-0.27-1-any downloading...
&lt;&#x2F;span&gt;&lt;span&gt;-  mingw-w64-x86_64-gcc-libs-14.2.0-2-any downloading...
&lt;&#x2F;span&gt;&lt;span&gt;-  mingw-w64-x86_64-libiconv-1.18-1-any downloading...
&lt;&#x2F;span&gt;&lt;span&gt;-  mingw-w64-x86_64-zstd-1.5.7-1-any downloading...
&lt;&#x2F;span&gt;&lt;span&gt;-  mingw-w64-x86_64-gmp-6.3.0-2-any downloading...
&lt;&#x2F;span&gt;&lt;span&gt;-  mingw-w64-x86_64-mpfr-4.2.1-2-any downloading...
&lt;&#x2F;span&gt;&lt;span&gt;-  mingw-w64-x86_64-gettext-runtime-0.23.1-1-any downloading...
&lt;&#x2F;span&gt;&lt;span&gt;-  mingw-w64-x86_64-mpc-1.3.1-2-any downloading...
&lt;&#x2F;span&gt;&lt;span&gt;-  mingw-w64-x86_64-zlib-1.3.1-1-any downloading...
&lt;&#x2F;span&gt;&lt;span&gt;-  mingw-w64-x86_64-winpthreads-git-12.0.0.r509.g079e6092b-1-any downloading...
&lt;&#x2F;span&gt;&lt;span&gt;-  mingw-w64-x86_64-libwinpthread-git-12.0.0.r509.g079e6092b-1-any downloading...
&lt;&#x2F;span&gt;&lt;span&gt;-  mingw-w64-x86_64-windows-default-manifest-6.4-4-any downloading...
&lt;&#x2F;span&gt;&lt;span&gt;- checking keyring...
&lt;&#x2F;span&gt;&lt;span&gt;- checking package integrity...
&lt;&#x2F;span&gt;&lt;span&gt;- loading package files...
&lt;&#x2F;span&gt;&lt;span&gt;- checking for file conflicts...
&lt;&#x2F;span&gt;&lt;span&gt;- checking available disk space...
&lt;&#x2F;span&gt;&lt;span&gt;- :: Processing package changes...
&lt;&#x2F;span&gt;&lt;span&gt;- installing mingw-w64-x86_64-libwinpthread-git...
&lt;&#x2F;span&gt;&lt;span&gt;- installing mingw-w64-x86_64-gcc-libs...
&lt;&#x2F;span&gt;&lt;span&gt;- installing mingw-w64-x86_64-libiconv...
&lt;&#x2F;span&gt;&lt;span&gt;- installing mingw-w64-x86_64-gettext-runtime...
&lt;&#x2F;span&gt;&lt;span&gt;- installing mingw-w64-x86_64-zlib...
&lt;&#x2F;span&gt;&lt;span&gt;- installing mingw-w64-x86_64-zstd...
&lt;&#x2F;span&gt;&lt;span&gt;- installing mingw-w64-x86_64-binutils...
&lt;&#x2F;span&gt;&lt;span&gt;- installing mingw-w64-x86_64-headers-git...
&lt;&#x2F;span&gt;&lt;span&gt;- installing mingw-w64-x86_64-crt-git...
&lt;&#x2F;span&gt;&lt;span&gt;- installing mingw-w64-x86_64-gmp...
&lt;&#x2F;span&gt;&lt;span&gt;- installing mingw-w64-x86_64-isl...
&lt;&#x2F;span&gt;&lt;span&gt;- installing mingw-w64-x86_64-mpfr...
&lt;&#x2F;span&gt;&lt;span&gt;- installing mingw-w64-x86_64-mpc...
&lt;&#x2F;span&gt;&lt;span&gt;- installing mingw-w64-x86_64-windows-default-manifest...
&lt;&#x2F;span&gt;&lt;span&gt;- installing mingw-w64-x86_64-winpthreads-git...
&lt;&#x2F;span&gt;&lt;span&gt;- installing mingw-w64-x86_64-gcc...
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt; Processing actions &amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;  🐫
&lt;&#x2F;span&gt;&lt;span&gt;∗ installed arch-x86_64.1
&lt;&#x2F;span&gt;&lt;span&gt;∗ installed base-bigarray.base
&lt;&#x2F;span&gt;&lt;span&gt;∗ installed base-threads.base
&lt;&#x2F;span&gt;&lt;span&gt;∗ installed base-unix.base
&lt;&#x2F;span&gt;&lt;span&gt;∗ installed host-arch-x86_64.1
&lt;&#x2F;span&gt;&lt;span&gt;∗ installed host-system-mingw.1
&lt;&#x2F;span&gt;&lt;span&gt;∗ installed msys2-mingw64.1
&lt;&#x2F;span&gt;&lt;span&gt;⬇ retrieved msys2.0.1.0  (https:&#x2F;&#x2F;opam.ocaml.org&#x2F;cache)
&lt;&#x2F;span&gt;&lt;span&gt;∗ installed msys2.0.1.0
&lt;&#x2F;span&gt;&lt;span&gt;∗ installed conf-mingw-w64-gcc-x86_64.1
&lt;&#x2F;span&gt;&lt;span&gt;⬇ retrieved flexdll.0.44  (https:&#x2F;&#x2F;opam.ocaml.org&#x2F;cache)
&lt;&#x2F;span&gt;&lt;span&gt;∗ installed flexdll.0.44
&lt;&#x2F;span&gt;&lt;span&gt;∗ installed ocaml-env-mingw64.1
&lt;&#x2F;span&gt;&lt;span&gt;∗ installed ocaml-options-vanilla.1
&lt;&#x2F;span&gt;&lt;span&gt;∗ installed system-mingw.1
&lt;&#x2F;span&gt;&lt;span&gt;⬇ retrieved ocaml-config.3  (2 extra sources)
&lt;&#x2F;span&gt;&lt;span&gt;⬇ retrieved ocaml-config.3  (2 extra sources)
&lt;&#x2F;span&gt;&lt;span&gt;⬇ retrieved ocaml-compiler.5.3.0  (https:&#x2F;&#x2F;opam.ocaml.org&#x2F;cache)
&lt;&#x2F;span&gt;&lt;span&gt;∗ installed ocaml-compiler.5.3.0
&lt;&#x2F;span&gt;&lt;span&gt;∗ installed ocaml-base-compiler.5.3.0
&lt;&#x2F;span&gt;&lt;span&gt;∗ installed ocaml-config.3
&lt;&#x2F;span&gt;&lt;span&gt;∗ installed ocaml.5.3.0
&lt;&#x2F;span&gt;&lt;span&gt;∗ installed base-domains.base
&lt;&#x2F;span&gt;&lt;span&gt;∗ installed base-effects.base
&lt;&#x2F;span&gt;&lt;span&gt;∗ installed base-nnp.base
&lt;&#x2F;span&gt;&lt;span&gt;Done.
&lt;&#x2F;span&gt;&lt;span&gt;# To update the current shell environment, run: (&amp;amp; opam env --switch=default) -split &amp;#39;\r?\n&amp;#39; | ForEach-Object { Invoke-Expression $_ }
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;During initialization, Opam took care of installing a C compiler and various
other essential tools into the Msys2 environment at &lt;code&gt;C:\msys64&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;My next goal is to get Opam to install libao with Msys2. I’m not sure what the
correct &lt;code&gt;depexts&lt;&#x2F;code&gt; entry should be for this, so I’ll start with &lt;code&gt;[&quot;libao&quot;] {os = &quot;win32&quot; &amp;amp; os-distribution = &quot;cygwin&quot;}&lt;&#x2F;code&gt;. I updated my custom copy of the
&lt;code&gt;conf-ao&lt;&#x2F;code&gt; package and pinned the package with Opam:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;PS C:\Users\s\conf-ao&amp;gt; opam pin .\conf-ao.opam
&lt;&#x2F;span&gt;&lt;span&gt;Fatal error:
&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;rsync&amp;quot;: command not found.
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;I guess I need to manually install &lt;code&gt;rsync&lt;&#x2F;code&gt; into my Msys2 environment? To do
this, launch an Msys2 terminal by running the &lt;code&gt;msys2.exe&lt;&#x2F;code&gt; program from the Msys2
installation (&lt;code&gt;C:\msys64\msys2.exe&lt;&#x2F;code&gt;). Msys2 uses &lt;code&gt;pacman&lt;&#x2F;code&gt; as its package
manager, borrowed from Archlinux:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;$ pacman -S rsync
&lt;&#x2F;span&gt;&lt;span&gt;warning: rsync-3.4.1-1 is up to date -- reinstalling
&lt;&#x2F;span&gt;&lt;span&gt;resolving dependencies...
&lt;&#x2F;span&gt;&lt;span&gt;looking for conflicting packages...
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;Packages (1) rsync-3.4.1-1
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;Total Installed Size:  0.67 MiB
&lt;&#x2F;span&gt;&lt;span&gt;Net Upgrade Size:      0.00 MiB
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;:: Proceed with installation? [Y&#x2F;n] n
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Ok looks like &lt;code&gt;rsync&lt;&#x2F;code&gt; was already installed.&lt;&#x2F;p&gt;
&lt;p&gt;Maybe when Opam isn’t managing Cygwin for us we need to to manually
update the shell environment to bring various commands into PATH?&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;PS C:\Users\s\conf-ao&amp;gt; (&amp;amp; opam env --switch=default) -split &amp;#39;\r?\n&amp;#39; | ForEach-Object { Invoke-Expression $_ }
&lt;&#x2F;span&gt;&lt;span&gt;PS C:\Users\s\conf-ao&amp;gt; opam pin .\conf-ao.opam
&lt;&#x2F;span&gt;&lt;span&gt;Fatal error:
&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;rsync&amp;quot;: command not found.
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;No such luck.&lt;&#x2F;p&gt;
&lt;p&gt;Checking that &lt;code&gt;rsync&lt;&#x2F;code&gt; works as expected:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;PS C:\Users\s\conf-ao&amp;gt; C:\msys64\usr\bin\rsync --help
&lt;&#x2F;span&gt;&lt;span&gt;rsync  version 3.4.1  protocol version 32
&lt;&#x2F;span&gt;&lt;span&gt;Copyright (C) 1996-2025 by Andrew Tridgell, Wayne Davison, and others.
&lt;&#x2F;span&gt;&lt;span&gt;Web site: https:&#x2F;&#x2F;rsync.samba.org&#x2F;
&lt;&#x2F;span&gt;&lt;span&gt;...
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Maybe I just need to manually add &lt;code&gt;C:\msys64\usr\bin&lt;&#x2F;code&gt; to my PATH.&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;PS C:\Users\s\conf-ao&amp;gt; $env:PATH += &amp;quot;;C:\msys64\usr\bin&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;PS C:\Users\s\conf-ao&amp;gt; rsync --version
&lt;&#x2F;span&gt;&lt;span&gt;rsync  version 3.4.1  protocol version 32
&lt;&#x2F;span&gt;&lt;span&gt;Copyright (C) 1996-2025 by Andrew Tridgell, Wayne Davison, and others.
&lt;&#x2F;span&gt;&lt;span&gt;Web site: https:&#x2F;&#x2F;rsync.samba.org&#x2F;
&lt;&#x2F;span&gt;&lt;span&gt;...
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;And now pinning &lt;code&gt;conf-ao&lt;&#x2F;code&gt; again:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;PS C:\Users\s\conf-ao&amp;gt; opam pin .\conf-ao.opam
&lt;&#x2F;span&gt;&lt;span&gt;[ERROR] Could not retrieve Could not extract archive:
&lt;&#x2F;span&gt;&lt;span&gt;        Unknown archive type: C:\Users\s\AppData\Local\Temp\opam-4184-d29c4e\conf-ao.opam
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Which reminds me that I’m not supposed to &lt;code&gt;pin&lt;&#x2F;code&gt; the opam file directly but
instead the entire folder which contains it.&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;PS C:\Users\s\conf-ao&amp;gt; opam pin .
&lt;&#x2F;span&gt;&lt;span&gt;conf-ao is now pinned to file:&#x2F;&#x2F;C:&#x2F;Users&#x2F;s&#x2F;conf-ao (version 1)
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;The following actions will be performed:
&lt;&#x2F;span&gt;&lt;span&gt;=== install 3 packages
&lt;&#x2F;span&gt;&lt;span&gt;  ∗ conf-ao                       1 (pinned)
&lt;&#x2F;span&gt;&lt;span&gt;  ∗ conf-mingw-w64-pkgconf-x86_64 1          [required by conf-pkg-config]
&lt;&#x2F;span&gt;&lt;span&gt;  ∗ conf-pkg-config               4          [required by conf-ao]
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;Proceed with ∗ 3 installations? [y&#x2F;n] n
&lt;&#x2F;span&gt;&lt;span&gt;[NOTE] Pinning command successful, but your installed packages may be out of sync.
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;That’s good.
I’m curious about the
&lt;code&gt;conf-mingw-w64-pkgconf-x86_64&lt;&#x2F;code&gt; package. It
wasn’t needed when installing
&lt;code&gt;depexts&lt;&#x2F;code&gt; with Cygwin, so it’s probably specific to Msys2, which means it can
probably give us a hint as to the proper way to specify Msys2-specific
&lt;code&gt;depexts&lt;&#x2F;code&gt;, which we’ll need to do for our pinned &lt;code&gt;conf-ao&lt;&#x2F;code&gt; package (I was only
guessing how to add its new &lt;code&gt;depexts&lt;&#x2F;code&gt; initially).&lt;&#x2F;p&gt;
&lt;p&gt;&lt;code&gt;conf-mingw-w64-pkgconf-x86_64&lt;&#x2F;code&gt; is probably a dependency of &lt;code&gt;conf-pkg-config&lt;&#x2F;code&gt; on
Msys2, so I’ll start by looking at &lt;code&gt;conf-pkg-config&lt;&#x2F;code&gt;’s metadata and then look at
the metadata for &lt;code&gt;conf-mingw-w64-pkgconf-x86_64&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;PS C:\Users\s\conf-ao&amp;gt; opam show --raw conf-pkg-config
&lt;&#x2F;span&gt;&lt;span&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;depends: [
&lt;&#x2F;span&gt;&lt;span&gt;  (&amp;quot;host-arch-x86_64&amp;quot; {os = &amp;quot;win32&amp;quot; &amp;amp; os-distribution = &amp;quot;msys2&amp;quot;} &amp;amp;
&lt;&#x2F;span&gt;&lt;span&gt;   &amp;quot;conf-mingw-w64-pkgconf-x86_64&amp;quot; {os = &amp;quot;win32&amp;quot; &amp;amp; os-distribution = &amp;quot;msys2&amp;quot;} |
&lt;&#x2F;span&gt;&lt;span&gt;   &amp;quot;host-arch-x86_32&amp;quot; {os = &amp;quot;win32&amp;quot; &amp;amp; os-distribution = &amp;quot;msys2&amp;quot;} &amp;amp;
&lt;&#x2F;span&gt;&lt;span&gt;   &amp;quot;conf-mingw-w64-pkgconf-i686&amp;quot; {os = &amp;quot;win32&amp;quot; &amp;amp; os-distribution = &amp;quot;msys2&amp;quot;})
&lt;&#x2F;span&gt;&lt;span&gt;]
&lt;&#x2F;span&gt;&lt;span&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;PS C:\Users\s\conf-ao&amp;gt; opam show --raw conf-mingw-w64-pkgconf-x86_64
&lt;&#x2F;span&gt;&lt;span&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;depexts:
&lt;&#x2F;span&gt;&lt;span&gt;  [&amp;quot;mingw-w64-x86_64-pkgconf&amp;quot;] {os = &amp;quot;win32&amp;quot; &amp;amp; os-distribution = &amp;quot;msys2&amp;quot;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;From this we can learn that when using Msys2 to install &lt;code&gt;depexts&lt;&#x2F;code&gt;, the
&lt;code&gt;os-distribution&lt;&#x2F;code&gt; variable evaluates to “msys2” instead of “cygwin”. Also on
Msys2 Opam will install the system package &lt;code&gt;mingw-w64-x86_64-pkgconf&lt;&#x2F;code&gt; to
install &lt;code&gt;pkg-config&lt;&#x2F;code&gt;&#x2F;&lt;code&gt;pkgconf&lt;&#x2F;code&gt;. This suggests that the correct &lt;code&gt;depext&lt;&#x2F;code&gt; entry
for &lt;code&gt;conf-ao&lt;&#x2F;code&gt; is in fact:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;[&amp;quot;mingw-w64-x86_64-libao&amp;quot;] {os = &amp;quot;win32&amp;quot; &amp;amp; os-distribution = &amp;quot;msys2&amp;quot;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;After updating the local copy of &lt;code&gt;conf-ao&lt;&#x2F;code&gt; with the above &lt;code&gt;depext&lt;&#x2F;code&gt; and
re-pinning it I tried installing &lt;code&gt;conf-ao&lt;&#x2F;code&gt; again:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;PS C:\Users\s\conf-ao&amp;gt; opam install conf-ao
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt; Synchronising pinned packages &amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;  🐫
&lt;&#x2F;span&gt;&lt;span&gt;[conf-ao.1] synchronised (no changes)
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;The following actions will be performed:
&lt;&#x2F;span&gt;&lt;span&gt;=== install 3 packages
&lt;&#x2F;span&gt;&lt;span&gt;  ∗ conf-ao                       1 (pinned)
&lt;&#x2F;span&gt;&lt;span&gt;  ∗ conf-mingw-w64-pkgconf-x86_64 1          [required by conf-pkg-config]
&lt;&#x2F;span&gt;&lt;span&gt;  ∗ conf-pkg-config               4          [required by conf-ao]
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;Proceed with ∗ 3 installations? [y&#x2F;n] y
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;The following system packages will first need to be installed:
&lt;&#x2F;span&gt;&lt;span&gt;    mingw-w64-x86_64-libao mingw-w64-x86_64-pkgconf
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt; Handling external dependencies &amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;  🐫
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;opam believes some required external dependencies are missing. opam can:
&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt; 1. Run C:\msys64\usr\bin\pacman.exe to install them (may need root&#x2F;sudo access)
&lt;&#x2F;span&gt;&lt;span&gt;  2. Display the recommended C:\msys64\usr\bin\pacman.exe command and wait while you run it manually (e.g. in another terminal)
&lt;&#x2F;span&gt;&lt;span&gt;  3. Continue anyway, and, upon success, permanently register that this external dependency is present, but not detectable
&lt;&#x2F;span&gt;&lt;span&gt;  4. Abort the installation
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;[1&#x2F;2&#x2F;3&#x2F;4] 1
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;+ C:\msys64\usr\bin\pacman.exe &amp;quot;-Su&amp;quot; &amp;quot;--noconfirm&amp;quot; &amp;quot;mingw-w64-x86_64-libao&amp;quot; &amp;quot;mingw-w64-x86_64-pkgconf&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;- :: Starting core system upgrade...
&lt;&#x2F;span&gt;&lt;span&gt;-  there is nothing to do
&lt;&#x2F;span&gt;&lt;span&gt;- :: Starting full system upgrade...
&lt;&#x2F;span&gt;&lt;span&gt;- resolving dependencies...
&lt;&#x2F;span&gt;&lt;span&gt;- looking for conflicting packages...
&lt;&#x2F;span&gt;&lt;span&gt;-
&lt;&#x2F;span&gt;&lt;span&gt;- Packages (2) mingw-w64-x86_64-libao-1.2.2-2  mingw-w64-x86_64-pkgconf-1~2.3.0-1
&lt;&#x2F;span&gt;&lt;span&gt;-
&lt;&#x2F;span&gt;&lt;span&gt;- Total Download Size:   0.16 MiB
&lt;&#x2F;span&gt;&lt;span&gt;- Total Installed Size:  0.77 MiB
&lt;&#x2F;span&gt;&lt;span&gt;-
&lt;&#x2F;span&gt;&lt;span&gt;- :: Proceed with installation? [Y&#x2F;n]
&lt;&#x2F;span&gt;&lt;span&gt;- :: Retrieving packages...
&lt;&#x2F;span&gt;&lt;span&gt;-  mingw-w64-x86_64-pkgconf-1~2.3.0-1-any downloading...
&lt;&#x2F;span&gt;&lt;span&gt;-  mingw-w64-x86_64-libao-1.2.2-2-any downloading...
&lt;&#x2F;span&gt;&lt;span&gt;- checking keyring...
&lt;&#x2F;span&gt;&lt;span&gt;- checking package integrity...
&lt;&#x2F;span&gt;&lt;span&gt;- loading package files...
&lt;&#x2F;span&gt;&lt;span&gt;- checking for file conflicts...
&lt;&#x2F;span&gt;&lt;span&gt;- checking available disk space...
&lt;&#x2F;span&gt;&lt;span&gt;- :: Processing package changes...
&lt;&#x2F;span&gt;&lt;span&gt;- installing mingw-w64-x86_64-libao...
&lt;&#x2F;span&gt;&lt;span&gt;- installing mingw-w64-x86_64-pkgconf...
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt; Processing actions &amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;  🐫
&lt;&#x2F;span&gt;&lt;span&gt;∗ installed conf-mingw-w64-pkgconf-x86_64.1
&lt;&#x2F;span&gt;&lt;span&gt;∗ installed conf-pkg-config.4
&lt;&#x2F;span&gt;&lt;span&gt;∗ installed conf-ao.1
&lt;&#x2F;span&gt;&lt;span&gt;Done.
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Success! And the fact that &lt;code&gt;conf-ao&lt;&#x2F;code&gt; installed successfully meant that
&lt;code&gt;conf-ao&lt;&#x2F;code&gt;’s build command which uses &lt;code&gt;pkgconf&lt;&#x2F;code&gt; to check whether the &lt;code&gt;ao&lt;&#x2F;code&gt; library
is visible also ran successfully, so we’re hopefully in a good place to link
against &lt;code&gt;ao&lt;&#x2F;code&gt; when building &lt;code&gt;llama&lt;&#x2F;code&gt;, which will be the next step.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;making-noise&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#making-noise&quot; aria-label=&quot;Anchor link for: making-noise&quot;&gt;Making Noise&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;Let’s try building and running an example from the &lt;code&gt;llama&lt;&#x2F;code&gt; synthesizer library.&lt;&#x2F;p&gt;
&lt;p&gt;Get the code:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;PS C:\Users\s&amp;gt; git clone git@github.com:gridbugs&#x2F;llama
&lt;&#x2F;span&gt;&lt;span&gt;Cloning into &amp;#39;llama&amp;#39;...
&lt;&#x2F;span&gt;&lt;span&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;Resolving deltas: 100% (961&#x2F;961), done.
&lt;&#x2F;span&gt;&lt;span&gt;PS C:\Users\s&amp;gt; cd .\llama\
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Since an old version of the packages in this repo are already published to Opam,
we need to pin them with Opam so the local versions are used and not the
released versions.&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;PS C:\Users\s\llama&amp;gt; opam pin .
&lt;&#x2F;span&gt;&lt;span&gt;This will pin the following packages: llama, llama_core, llama_interactive, llama_midi, llama_tests. Continue? [y&#x2F;n] y
&lt;&#x2F;span&gt;&lt;span&gt;llama is now pinned to git+file:&#x2F;&#x2F;C:&#x2F;Users&#x2F;s&#x2F;llama#main (version 0.1.0)
&lt;&#x2F;span&gt;&lt;span&gt;llama_core is now pinned to git+file:&#x2F;&#x2F;C:&#x2F;Users&#x2F;s&#x2F;llama#main (version 0.1.0)
&lt;&#x2F;span&gt;&lt;span&gt;llama_interactive is now pinned to git+file:&#x2F;&#x2F;C:&#x2F;Users&#x2F;s&#x2F;llama#main (version 0.1.0)
&lt;&#x2F;span&gt;&lt;span&gt;llama_midi is now pinned to git+file:&#x2F;&#x2F;C:&#x2F;Users&#x2F;s&#x2F;llama#main (version 0.1.0)
&lt;&#x2F;span&gt;&lt;span&gt;Package llama_tests does not exist, create as a NEW package? [y&#x2F;n] y
&lt;&#x2F;span&gt;&lt;span&gt;llama_tests is now pinned to git+file:&#x2F;&#x2F;C:&#x2F;Users&#x2F;s&#x2F;llama#main (version dev)
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;[ERROR] Package conflict!
&lt;&#x2F;span&gt;&lt;span&gt;  * Missing dependency:
&lt;&#x2F;span&gt;&lt;span&gt;    - llama_tests → llama_midi &amp;gt;= dev
&lt;&#x2F;span&gt;&lt;span&gt;    no matching version
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;[NOTE] Pinning command successful, but your installed packages may be out of sync.
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;That error is because the &lt;code&gt;llama_tests&lt;&#x2F;code&gt; library is unreleased while all the
other packages are released. Opam phones home while pinning packages to work out which
versions to pin (it uses the latest released version of each
released package and “dev” for unreleased packages from the look of things).
That’s not ideal here though because the unreleased &lt;code&gt;llama_tests&lt;&#x2F;code&gt; package is
looking for a version of &lt;code&gt;llama_midi&lt;&#x2F;code&gt; of its same version and failing to find it
because the unreleased &lt;code&gt;llama_tests&lt;&#x2F;code&gt; is version “dev” while &lt;code&gt;llama_midi&lt;&#x2F;code&gt; is
version “0.1.0”. I think we can work around this by forcing Opam to pin all the
packages at version “dev”:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;PS C:\Users\s\llama&amp;gt; opam pin . --with-version=dev
&lt;&#x2F;span&gt;&lt;span&gt;This will pin the following packages: llama, llama_core, llama_interactive, llama_midi, llama_tests. Continue? [y&#x2F;n] y
&lt;&#x2F;span&gt;&lt;span&gt;[NOTE] Package llama is currently pinned to git+file:&#x2F;&#x2F;C:&#x2F;Users&#x2F;s&#x2F;llama#main (version 0.1.0).
&lt;&#x2F;span&gt;&lt;span&gt;llama is now pinned to git+file:&#x2F;&#x2F;C:&#x2F;Users&#x2F;s&#x2F;llama#main (version dev)
&lt;&#x2F;span&gt;&lt;span&gt;[NOTE] Package llama_core is currently pinned to git+file:&#x2F;&#x2F;C:&#x2F;Users&#x2F;s&#x2F;llama#main (version 0.1.0).
&lt;&#x2F;span&gt;&lt;span&gt;llama_core is now pinned to git+file:&#x2F;&#x2F;C:&#x2F;Users&#x2F;s&#x2F;llama#main (version dev)
&lt;&#x2F;span&gt;&lt;span&gt;[NOTE] Package llama_interactive is currently pinned to git+file:&#x2F;&#x2F;C:&#x2F;Users&#x2F;s&#x2F;llama#main (version 0.1.0).
&lt;&#x2F;span&gt;&lt;span&gt;llama_interactive is now pinned to git+file:&#x2F;&#x2F;C:&#x2F;Users&#x2F;s&#x2F;llama#main (version dev)
&lt;&#x2F;span&gt;&lt;span&gt;[NOTE] Package llama_midi is currently pinned to git+file:&#x2F;&#x2F;C:&#x2F;Users&#x2F;s&#x2F;llama#main (version 0.1.0).
&lt;&#x2F;span&gt;&lt;span&gt;llama_midi is now pinned to git+file:&#x2F;&#x2F;C:&#x2F;Users&#x2F;s&#x2F;llama#main (version dev)
&lt;&#x2F;span&gt;&lt;span&gt;[NOTE] Package llama_tests is already pinned to git+file:&#x2F;&#x2F;C:&#x2F;Users&#x2F;s&#x2F;llama#main (version dev).
&lt;&#x2F;span&gt;&lt;span&gt;llama_tests is now pinned to git+file:&#x2F;&#x2F;C:&#x2F;Users&#x2F;s&#x2F;llama#main (version dev)
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;The following actions will be performed:
&lt;&#x2F;span&gt;&lt;span&gt;=== install 44 packages
&lt;&#x2F;span&gt;&lt;span&gt;  ∗ ao                           0.2.4        [required by llama]
&lt;&#x2F;span&gt;&lt;span&gt;  ∗ base                         v0.17.2      [required by ppx_inline_test]
&lt;&#x2F;span&gt;&lt;span&gt;  ∗ bigarray-compat              1.1.0        [required by ctypes]
&lt;&#x2F;span&gt;&lt;span&gt;  ∗ conf-libffi                  2.0.0        [required by ctypes-foreign]
&lt;&#x2F;span&gt;&lt;span&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;Proceed with ∗ 44 installations? [y&#x2F;n] n
&lt;&#x2F;span&gt;&lt;span&gt;[NOTE] Pinning command successful, but your installed packages may be out of sync.
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Just checking that we now don’t have 2 pins for each released package (“0.1.0”
and “dev”) from the two different runs of &lt;code&gt;opam pin&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;PS C:\Users\s\llama&amp;gt; opam pin list
&lt;&#x2F;span&gt;&lt;span&gt;conf-ao.1                             rsync  file:&#x2F;&#x2F;C:&#x2F;Users&#x2F;s&#x2F;conf-ao
&lt;&#x2F;span&gt;&lt;span&gt;llama.dev              (uninstalled)  git    git+file:&#x2F;&#x2F;C:&#x2F;Users&#x2F;s&#x2F;llama#main
&lt;&#x2F;span&gt;&lt;span&gt;llama_core.dev         (uninstalled)  git    git+file:&#x2F;&#x2F;C:&#x2F;Users&#x2F;s&#x2F;llama#main
&lt;&#x2F;span&gt;&lt;span&gt;llama_interactive.dev  (uninstalled)  git    git+file:&#x2F;&#x2F;C:&#x2F;Users&#x2F;s&#x2F;llama#main
&lt;&#x2F;span&gt;&lt;span&gt;llama_midi.dev         (uninstalled)  git    git+file:&#x2F;&#x2F;C:&#x2F;Users&#x2F;s&#x2F;llama#main
&lt;&#x2F;span&gt;&lt;span&gt;llama_tests.dev        (uninstalled)  git    git+file:&#x2F;&#x2F;C:&#x2F;Users&#x2F;s&#x2F;llama#main
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Looks good, and also we can see that our custom &lt;code&gt;conf-ao&lt;&#x2F;code&gt; is there which is
expected. One potential issue is that the &lt;code&gt;llama&lt;&#x2F;code&gt; packages are fetched with &lt;code&gt;git&lt;&#x2F;code&gt;
rather than &lt;code&gt;rsync&lt;&#x2F;code&gt;, which might bite us later if we modify the local files
in the &lt;code&gt;llama&lt;&#x2F;code&gt; project without committing the results. We can avoid this by not
installing any of these local packages with Opam, and instead just installing
their dependencies and then building the project with Dune.&lt;&#x2F;p&gt;
&lt;p&gt;Install the deps:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;PS C:\Users\s\llama&amp;gt; opam install . --deps-only
&lt;&#x2F;span&gt;&lt;span&gt;[llama.dev] synchronised (no changes)
&lt;&#x2F;span&gt;&lt;span&gt;[llama_core.dev] synchronised (no changes)
&lt;&#x2F;span&gt;&lt;span&gt;[llama_interactive.dev] synchronised (no changes)
&lt;&#x2F;span&gt;&lt;span&gt;[llama_midi.dev] synchronised (no changes)
&lt;&#x2F;span&gt;&lt;span&gt;[llama_tests.dev] synchronised (no changes)
&lt;&#x2F;span&gt;&lt;span&gt;The following actions will be performed:
&lt;&#x2F;span&gt;&lt;span&gt;=== install 39 packages
&lt;&#x2F;span&gt;&lt;span&gt;  ∗ ao                           0.2.4   [required by llama]
&lt;&#x2F;span&gt;&lt;span&gt;  ∗ base                         v0.17.2 [required by ppx_inline_test]
&lt;&#x2F;span&gt;&lt;span&gt;  ∗ bigarray-compat              1.1.0   [required by ctypes]
&lt;&#x2F;span&gt;&lt;span&gt;  ∗ conf-libffi                  2.0.0   [required by ctypes-foreign]
&lt;&#x2F;span&gt;&lt;span&gt;  ∗ conf-mingw-w64-libffi-x86_64 1       [required by conf-libffi]
&lt;&#x2F;span&gt;&lt;span&gt;  ∗ conf-mingw-w64-sdl2-x86_64   1       [required by conf-sdl2]
&lt;&#x2F;span&gt;&lt;span&gt;  ∗ conf-sdl2                    1       [required by tsdl]
&lt;&#x2F;span&gt;&lt;span&gt;  ∗ csexp                        1.5.2   [required by dune-configurator]
&lt;&#x2F;span&gt;&lt;span&gt;  ∗ ctypes                       0.23.0  [required by tsdl]
&lt;&#x2F;span&gt;&lt;span&gt;  ∗ ctypes-foreign               0.23.0  [required by tsdl]
&lt;&#x2F;span&gt;&lt;span&gt;  ∗ dune                         3.19.0  [required by llama_midi, llama_core, llama, etc.]
&lt;&#x2F;span&gt;&lt;span&gt;  ∗ dune-configurator            3.19.0  [required by ao]
&lt;&#x2F;span&gt;&lt;span&gt;  ∗ integers                     0.7.0   [required by ctypes]
&lt;&#x2F;span&gt;&lt;span&gt;  ∗ jane-street-headers          v0.17.0 [required by time_now]
&lt;&#x2F;span&gt;&lt;span&gt;  ∗ jst-config                   v0.17.0 [required by time_now]
&lt;&#x2F;span&gt;&lt;span&gt;  ∗ ocaml-compiler-libs          v0.17.0 [required by ppxlib]
&lt;&#x2F;span&gt;&lt;span&gt;  ∗ ocaml_intrinsics_kernel      v0.17.1 [required by base]
&lt;&#x2F;span&gt;&lt;span&gt;  ∗ ocamlbuild                   0.16.1  [required by tsdl]
&lt;&#x2F;span&gt;&lt;span&gt;  ∗ ocamlfind                    1.9.8   [required by tsdl]
&lt;&#x2F;span&gt;&lt;span&gt;  ∗ ppx_assert                   v0.17.0 [required by jst-config]
&lt;&#x2F;span&gt;&lt;span&gt;  ∗ ppx_base                     v0.17.0 [required by time_now]
&lt;&#x2F;span&gt;&lt;span&gt;  ∗ ppx_cold                     v0.17.0 [required by ppx_base]
&lt;&#x2F;span&gt;&lt;span&gt;  ∗ ppx_compare                  v0.17.0 [required by ppx_base]
&lt;&#x2F;span&gt;&lt;span&gt;  ∗ ppx_derivers                 1.2.1   [required by ppxlib]
&lt;&#x2F;span&gt;&lt;span&gt;  ∗ ppx_enumerate                v0.17.0 [required by ppx_base]
&lt;&#x2F;span&gt;&lt;span&gt;  ∗ ppx_globalize                v0.17.0 [required by ppx_base]
&lt;&#x2F;span&gt;&lt;span&gt;  ∗ ppx_hash                     v0.17.0 [required by ppx_base]
&lt;&#x2F;span&gt;&lt;span&gt;  ∗ ppx_here                     v0.17.0 [required by ppx_assert]
&lt;&#x2F;span&gt;&lt;span&gt;  ∗ ppx_inline_test              v0.17.0 [required by llama_tests]
&lt;&#x2F;span&gt;&lt;span&gt;  ∗ ppx_optcomp                  v0.17.0 [required by time_now]
&lt;&#x2F;span&gt;&lt;span&gt;  ∗ ppx_sexp_conv                v0.17.0 [required by ppx_base]
&lt;&#x2F;span&gt;&lt;span&gt;  ∗ ppxlib                       0.35.0  [required by ppx_inline_test]
&lt;&#x2F;span&gt;&lt;span&gt;  ∗ ppxlib_jane                  v0.17.2 [required by ppx_globalize, ppx_enumerate, ppx_hash]
&lt;&#x2F;span&gt;&lt;span&gt;  ∗ sexplib0                     v0.17.0 [required by base, ppxlib]
&lt;&#x2F;span&gt;&lt;span&gt;  ∗ stdio                        v0.17.0 [required by ppx_optcomp]
&lt;&#x2F;span&gt;&lt;span&gt;  ∗ stdlib-shims                 0.3.0   [required by ppxlib]
&lt;&#x2F;span&gt;&lt;span&gt;  ∗ time_now                     v0.17.0 [required by ppx_inline_test]
&lt;&#x2F;span&gt;&lt;span&gt;  ∗ topkg                        1.0.8   [required by tsdl]
&lt;&#x2F;span&gt;&lt;span&gt;  ∗ tsdl                         1.1.0   [required by llama_interactive]
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;Proceed with ∗ 39 installations? [y&#x2F;n] y
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;The following system packages will first need to be installed:
&lt;&#x2F;span&gt;&lt;span&gt;    mingw-w64-x86_64-libffi mingw-w64-x86_64-SDL2
&lt;&#x2F;span&gt;&lt;span&gt;...
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Note the additional depexts &lt;code&gt;mingw-w64-x86_64-libffi&lt;&#x2F;code&gt; and
&lt;code&gt;mingw-w64-x86_64-SDL2&lt;&#x2F;code&gt;. Based on their names these appear to be packages in the
Msys2 repo. It’s great that these packages don’t seem to require the manual
intervention needed for &lt;code&gt;libao&lt;&#x2F;code&gt;. While my project’s dependencies install, let’s
investigate how one of these packages works:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;PS C:\Users\s&amp;gt; opam show conf-sdl2
&lt;&#x2F;span&gt;&lt;span&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;depends     &amp;quot;conf-pkg-config&amp;quot; {build}
&lt;&#x2F;span&gt;&lt;span&gt;            (&amp;quot;host-arch-x86_32&amp;quot; {os = &amp;quot;win32&amp;quot; &amp;amp; os-distribution != &amp;quot;cygwinports&amp;quot;} &amp;amp;
&lt;&#x2F;span&gt;&lt;span&gt;             &amp;quot;conf-mingw-w64-sdl2-i686&amp;quot; {os = &amp;quot;win32&amp;quot; &amp;amp; os-distribution != &amp;quot;cygwinports&amp;quot;} |
&lt;&#x2F;span&gt;&lt;span&gt;             &amp;quot;host-arch-x86_64&amp;quot; {os = &amp;quot;win32&amp;quot; &amp;amp; os-distribution != &amp;quot;cygwinports&amp;quot;} &amp;amp;
&lt;&#x2F;span&gt;&lt;span&gt;             &amp;quot;conf-mingw-w64-sdl2-x86_64&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;               {os = &amp;quot;win32&amp;quot; &amp;amp; os-distribution != &amp;quot;cygwinports&amp;quot;})
&lt;&#x2F;span&gt;&lt;span&gt;...
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Similar to &lt;code&gt;conf-pkg-config&lt;&#x2F;code&gt;, we see the dependency on an additional
&lt;code&gt;conf-mingw-w64-*&lt;&#x2F;code&gt; metapackage under some conditions. Here though it’s
&lt;code&gt;os-distribution != &quot;cygwinports&quot;&lt;&#x2F;code&gt; rather than &lt;code&gt;os-distribution = &quot;msys2&quot;&lt;&#x2F;code&gt; which
makes me wonder if this would cause a problem on a regular Cygwin installation,
where &lt;code&gt;os-distribution = &quot;cygwin&quot;&lt;&#x2F;code&gt;. That would cause the &lt;code&gt;os-distribution != &quot;cygwinports&quot;&lt;&#x2F;code&gt;
to be true which seems to select &lt;code&gt;conf-mingw-w64-*&lt;&#x2F;code&gt; packages, but my intuition
is that those packages should only be selected on Msys2. There’s too much
else going on to also investigate that now and maybe my hunch is wrong anyway.&lt;&#x2F;p&gt;
&lt;p&gt;There’s a new problem: &lt;code&gt;llama&lt;&#x2F;code&gt;’s dependencies failed to install:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;#=== ERROR while compiling topkg.1.0.8 ========================================#
&lt;&#x2F;span&gt;&lt;span&gt;# context     2.3.0 | win32&#x2F;x86_64 | ocaml.5.3.0 | https:&#x2F;&#x2F;opam.ocaml.org#11859fd62a66b5e319a415e807797407068a7c13
&lt;&#x2F;span&gt;&lt;span&gt;# path        ~\AppData\Local\opam\default\.opam-switch\build\topkg.1.0.8
&lt;&#x2F;span&gt;&lt;span&gt;# command     ~\AppData\Local\opam\default\bin\ocaml.exe pkg&#x2F;pkg.ml build --pkg-name topkg --dev-pkg false
&lt;&#x2F;span&gt;&lt;span&gt;# exit-code   125
&lt;&#x2F;span&gt;&lt;span&gt;# env-file    ~\AppData\Local\opam\log\topkg-18684-d088ec.env
&lt;&#x2F;span&gt;&lt;span&gt;# output-file ~\AppData\Local\opam\log\topkg-18684-d088ec.out
&lt;&#x2F;span&gt;&lt;span&gt;### output ###
&lt;&#x2F;span&gt;&lt;span&gt;# Exception: Fl_package_base.No_such_package (&amp;quot;findlib&amp;quot;, &amp;quot;&amp;quot;).
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt; Error report &amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;  🐫
&lt;&#x2F;span&gt;&lt;span&gt;┌─ The following actions failed
&lt;&#x2F;span&gt;&lt;span&gt;│ λ build topkg 1.0.8
&lt;&#x2F;span&gt;&lt;span&gt;└─
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The &lt;a href=&quot;https:&#x2F;&#x2F;opam.ocaml.org&#x2F;packages&#x2F;topkg&#x2F;&quot;&gt;topkg&lt;&#x2F;a&gt; package is a build tool.
According to its description it’s in maintenance mode and should no longer be
used. It’s got 165 reverse dependencies so the fact that it doesn’t build on
Windows (or maybe just when using Msys2 or maybe just on my machine) is a little
concerning however for our purposes here it’s only used to build &lt;code&gt;tsdl&lt;&#x2F;code&gt; which is
only needed for the interactive components of &lt;code&gt;llama&lt;&#x2F;code&gt;. It would still be nice to
get it working but my priority is getting the sound to work, so consider fixing
&lt;code&gt;topkg&lt;&#x2F;code&gt; to be a stretch goal.&lt;&#x2F;p&gt;
&lt;p&gt;For now, let’s just install the dependencies needed for non-interactive
synthesizer demos:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;PS C:\Users\s\llama&amp;gt; opam install llama --deps-only
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt; Synchronising pinned packages &amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;  🐫
&lt;&#x2F;span&gt;&lt;span&gt;[llama.dev] synchronised (no changes)
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;The following actions will be performed:
&lt;&#x2F;span&gt;&lt;span&gt;=== install 2 packages
&lt;&#x2F;span&gt;&lt;span&gt;  ∗ llama_core dev (pinned) [required by llama]
&lt;&#x2F;span&gt;&lt;span&gt;  ∗ llama_midi dev (pinned) [required by llama_core]
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;Proceed with ∗ 2 installations? [y&#x2F;n] n
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Good news! All the necessary dependencies were successfully installed in the
previous step. Those two deps are local packages in this project so we don’t
need to install them with Opam. In theory we should now have everything we need
to run an example:&lt;&#x2F;p&gt;
&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https:&#x2F;&#x2F;www.youtube.com&#x2F;embed&#x2F;ZikuC-SCtEQ?si=EY2akELtCOS0Kfji&quot; title=&quot;YouTube video player&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&quot; referrerpolicy=&quot;strict-origin-when-cross-origin&quot; allowfullscreen&gt;&lt;&#x2F;iframe&gt;
&lt;p&gt;Honestly that was easier than I thought it would be. I’m really happy that I
didn’t need to manually install libao and that Msys2&#x2F;Opam could take care of
installing it and all the other system dependencies needed to build the synth
library and to access the sound card.&lt;&#x2F;p&gt;
&lt;p&gt;Most of what went wrong during this process could conceivably be called “user
error” though the fact that I have 5 years of OCaml programming behind me
and still hit some pretty major footguns with Opam maybe suggests some aspects
of its UX could be improved. Still some things were actually broken:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;conf-ao&lt;&#x2F;code&gt; is hardcoded to run &lt;code&gt;pkg-config&lt;&#x2F;code&gt; despite the executable being named
&lt;code&gt;pkgconf&lt;&#x2F;code&gt; in some cases, and has no depext for Msys2. The workaround was to
pin &lt;code&gt;conf-ao&lt;&#x2F;code&gt; and fix its problems in a local copy of the package.&lt;&#x2F;li&gt;
&lt;li&gt;Opam couldn’t find the &lt;code&gt;rsync&lt;&#x2F;code&gt; executable in the Msys2 environment despite
being configured to use an Msys2 environment where &lt;code&gt;rsync&lt;&#x2F;code&gt; was installed. The
workaround was to manually add &lt;code&gt;C:\msys64\usr\bin&lt;&#x2F;code&gt; to PATH.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;topkg&lt;&#x2F;code&gt; failed to build. The workaround was to only use the non-interactive
components of my project, as &lt;code&gt;topkg&lt;&#x2F;code&gt; is only necessary to compile the SDL bindings
which are only needed for interactive synthesizer features.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;The parts of Opam’s UX that I evidently still struggle with relate to working
with local packages and pins.
Looking back at the “user errors” in this post, a couple of times I tried to pin
an opam file led to bizarre errors (
&lt;code&gt;Unknown archive type: C:\Users\s\AppData\Local\Temp\opam-4184-d29c4e\conf-ao.opam&lt;&#x2F;code&gt;)
and unexpected results (recursively copying my home directory inside itself
until I told it to stop). Opam pins are associated with package sources in the
form of a directory, git repo, or archive file - not with opam files themselves.
What’s confusing when working with packages like &lt;code&gt;conf-ao&lt;&#x2F;code&gt; is that these
packages &lt;em&gt;don’t have sources&lt;&#x2F;em&gt;. They are metapackages that just exist to
collect some dependencies on other Opam packages or external dependencies, but
they have no source code or other files to fetch during installation. I’ve
learnt that the correct thing to do is put the opam file I want to pin inside a
directory and then pin that directory.&lt;&#x2F;p&gt;
&lt;p&gt;The other pinning-related mistake I made was pinning a project containing
multiple packages where some but not all of the packages had been released. This
led to the non-released packages being pinned at version “dev” while the
released packages are pinned to the latest released version of those packages.
If the unreleased package depends on a released package, chances are it won’t be
able to resolve the dependency because it will try to find a version of the
released package with version “dev”. I’ve learnt that for such projects one must
run &lt;code&gt;opam pin . --with-version=dev&lt;&#x2F;code&gt;. Possibly that should be the default
behaviour or at least Opam should warn when some but not all of the packages in
a project are released.
I’ve complained about this exact behaviour
&lt;a href=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;frustrating-interactions-with-the-ocaml-ecosystem-while-developing-a-synthesizer-library&#x2F;#if-some-but-not-all-of-the-interdependent-packages-in-a-project-are-released-opam-can-t-solve-the-project-s-dependencies&quot;&gt;before&lt;&#x2F;a&gt;
but I still make this mistake all the time.&lt;&#x2F;p&gt;
&lt;p&gt;I have gotten used to Opam’s quirks and am no longer too phased when things go
wrong. And on Windows in particular I had quite low expectations of the OCaml
ecosystem since it’s historically been infamously hard to get working. This is
why I’m pleasantly surprised it was this easy to get generated audio playing
on Windows with OCaml, despite spending over a day on it at this point. The fact
that Opam can be installed through WinGet now, and that it takes care of setting up all
the external dependencies with Msys2 (or Cygwin) for you during initialization
is very slick. It’s definitely come a long way since the last time I tried to
use OCaml on Windows in earnest.&lt;&#x2F;p&gt;
&lt;p&gt;That said many of the problems I had with Opam in this post were not
Windows-related. The problems with pinning are things I had encountered before
on Unix so I could recognize them and employ workarounds. I’ve adopted a
defensive approach to working with Opam where I expect the unexpected and often
explicitly verify that it did what I want such as always running &lt;code&gt;opam pin list&lt;&#x2F;code&gt;
after &lt;code&gt;opam pin&lt;&#x2F;code&gt; and using &lt;code&gt;opam show --raw&lt;&#x2F;code&gt; to check that pinning a package
has taken effect if I’m changing a pinned package’s metadata. I don’t believe
“user error” is an apt term to use here. The error is not with the user it’s
with the usability of the tool.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;stretch-goal-fixing-topkg&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#stretch-goal-fixing-topkg&quot; aria-label=&quot;Anchor link for: stretch-goal-fixing-topkg&quot;&gt;Stretch Goal: Fixing topkg&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;I set aside 2 days to get &lt;code&gt;llama&lt;&#x2F;code&gt; working on Windows and it’s only been a day and
a half, so let’s see if we can’t get &lt;code&gt;topkg&lt;&#x2F;code&gt; to build and then run the graphical
interactive synthesizer demos which that would enable.&lt;&#x2F;p&gt;
&lt;p&gt;We’ll start by grabbing the source code for topkg. Opam has a handy feature for
doing just this:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;PS C:\Users\s&amp;gt; opam source topkg
&lt;&#x2F;span&gt;&lt;span&gt;Successfully extracted to C:\Users\s\topkg.1.0.8
&lt;&#x2F;span&gt;&lt;span&gt;PS C:\Users\s&amp;gt; cd .\topkg.1.0.8\
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Now pin &lt;code&gt;topkg&lt;&#x2F;code&gt; to the local version so we can tweak things locally and test
out how they affect building &lt;code&gt;topkg&lt;&#x2F;code&gt; as a dependency:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;PS C:\Users\s\topkg.1.0.8&amp;gt; opam pin .
&lt;&#x2F;span&gt;&lt;span&gt;This will pin the following packages: topkg-care, topkg. Continue? [y&#x2F;n] y
&lt;&#x2F;span&gt;&lt;span&gt;topkg-care is now pinned to file:&#x2F;&#x2F;C:&#x2F;Users&#x2F;s&#x2F;topkg.1.0.8 (version 1.0.8)
&lt;&#x2F;span&gt;&lt;span&gt;topkg is now pinned to file:&#x2F;&#x2F;C:&#x2F;Users&#x2F;s&#x2F;topkg.1.0.8 (version 1.0.8)
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;The following actions will be performed:
&lt;&#x2F;span&gt;&lt;span&gt;=== remove 6 packages
&lt;&#x2F;span&gt;&lt;span&gt;  ⊘ base-domains            base               [conflicts with ocaml]
&lt;&#x2F;span&gt;&lt;span&gt;  ⊘ base-effects            base               [conflicts with ocaml]
&lt;&#x2F;span&gt;&lt;span&gt;  ⊘ base-nnp                base               [uses base-domains]
&lt;&#x2F;span&gt;&lt;span&gt;  ⊘ ocaml-compiler          5.3.0
&lt;&#x2F;span&gt;&lt;span&gt;  ⊘ ocaml_intrinsics_kernel v0.17.1            [conflicts with ocaml]
&lt;&#x2F;span&gt;&lt;span&gt;  ⊘ ppxlib_jane             v0.17.2            [conflicts with ocaml]
&lt;&#x2F;span&gt;&lt;span&gt;=== downgrade 20 packages
&lt;&#x2F;span&gt;&lt;span&gt;  ↘ base                    v0.17.2 to v0.16.4 [uses ocaml]
&lt;&#x2F;span&gt;&lt;span&gt;  ↘ jane-street-headers     v0.17.0 to v0.16.0 [uses ocaml]
&lt;&#x2F;span&gt;&lt;span&gt;  ↘ jst-config              v0.17.0 to v0.16.0 [uses ocaml]
&lt;&#x2F;span&gt;&lt;span&gt;  ↘ ocaml                   5.3.0 to 4.14.2    [required by topkg, topkg-care]
&lt;&#x2F;span&gt;&lt;span&gt;  ↘ ocaml-base-compiler     5.3.0 to 4.14.2    [required by ocaml]
&lt;&#x2F;span&gt;&lt;span&gt;  ↘ ocaml-compiler-libs     v0.17.0 to v0.12.4 [uses ocaml]
&lt;&#x2F;span&gt;&lt;span&gt;  ↘ ppx_assert              v0.17.0 to v0.16.0 [uses ocaml]
&lt;&#x2F;span&gt;&lt;span&gt;  ↘ ppx_base                v0.17.0 to v0.16.0 [uses ocaml]
&lt;&#x2F;span&gt;&lt;span&gt;  ↘ ppx_cold                v0.17.0 to v0.16.0 [uses ocaml]
&lt;&#x2F;span&gt;&lt;span&gt;  ↘ ppx_compare             v0.17.0 to v0.16.0 [uses ocaml]
&lt;&#x2F;span&gt;&lt;span&gt;  ↘ ppx_enumerate           v0.17.0 to v0.16.0 [uses ocaml]
&lt;&#x2F;span&gt;&lt;span&gt;  ↘ ppx_globalize           v0.17.0 to v0.16.0 [uses ocaml]
&lt;&#x2F;span&gt;&lt;span&gt;  ↘ ppx_hash                v0.17.0 to v0.16.0 [uses ocaml]
&lt;&#x2F;span&gt;&lt;span&gt;  ↘ ppx_here                v0.17.0 to v0.16.0 [uses ocaml]
&lt;&#x2F;span&gt;&lt;span&gt;  ↘ ppx_inline_test         v0.17.0 to v0.16.1 [uses ocaml]
&lt;&#x2F;span&gt;&lt;span&gt;  ↘ ppx_optcomp             v0.17.0 to v0.16.0 [uses ocaml]
&lt;&#x2F;span&gt;&lt;span&gt;  ↘ ppx_sexp_conv           v0.17.0 to v0.16.0 [uses ocaml]
&lt;&#x2F;span&gt;&lt;span&gt;  ↘ sexplib0                v0.17.0 to v0.16.0 [uses ocaml]
&lt;&#x2F;span&gt;&lt;span&gt;  ↘ stdio                   v0.17.0 to v0.16.0 [uses ocaml]
&lt;&#x2F;span&gt;&lt;span&gt;  ↘ time_now                v0.17.0 to v0.16.0 [uses ocaml]
&lt;&#x2F;span&gt;&lt;span&gt;...
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Opam wants to remove or downgrade lots of packages including the compiler
itself. That seems a bit drastic so let’s see if there’s a way to avoid doing
that. After all &lt;code&gt;topkg.1.0.8&lt;&#x2F;code&gt; was part of the package solution Opam found when
building &lt;code&gt;llama&lt;&#x2F;code&gt;’s dependencies and &lt;code&gt;ocaml.5.3.0&lt;&#x2F;code&gt; was sufficient in that solution.
It’s odd that it wouldn’t also be sufficient here.
The opam files in &lt;code&gt;topkg&lt;&#x2F;code&gt;’s source archive look like they should be compatible
with &lt;code&gt;ocaml.5.3.0&lt;&#x2F;code&gt; so I really have no idea why Opam wanted to downgrade
everything.&lt;&#x2F;p&gt;
&lt;p&gt;Regardless, we don’t actually need to install &lt;code&gt;topkg&lt;&#x2F;code&gt; while pinning it. It’s
pinned now so that should be sufficient for the local copy to be compiled as a
dependency of &lt;code&gt;llama&lt;&#x2F;code&gt;. Let’s see if that’s correct:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;PS C:\Users\s\llama&amp;gt; opam install . --deps-only
&lt;&#x2F;span&gt;&lt;span&gt;[llama_core.dev] synchronised (no changes)
&lt;&#x2F;span&gt;&lt;span&gt;[llama.dev] synchronised (no changes)
&lt;&#x2F;span&gt;&lt;span&gt;[llama_interactive.dev] synchronised (no changes)
&lt;&#x2F;span&gt;&lt;span&gt;[llama_midi.dev] synchronised (no changes)
&lt;&#x2F;span&gt;&lt;span&gt;[llama_tests.dev] synchronised (no changes)
&lt;&#x2F;span&gt;&lt;span&gt;The following actions will be performed:
&lt;&#x2F;span&gt;&lt;span&gt;=== install 2 packages
&lt;&#x2F;span&gt;&lt;span&gt;  ∗ topkg 1.0.8 (pinned) [required by tsdl]
&lt;&#x2F;span&gt;&lt;span&gt;  ∗ tsdl  1.1.0          [required by llama_interactive]
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;Proceed with ∗ 2 installations? [y&#x2F;n] y
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt; Processing actions &amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;  🐫
&lt;&#x2F;span&gt;&lt;span&gt;⬇ retrieved tsdl.1.1.0  (cached)
&lt;&#x2F;span&gt;&lt;span&gt;[ERROR] The compilation of topkg.1.0.8 failed at &amp;quot;ocaml pkg&#x2F;pkg.ml build --pkg-name topkg --dev-pkg true&amp;quot;.
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;#=== ERROR while compiling topkg.1.0.8 ========================================#
&lt;&#x2F;span&gt;&lt;span&gt;# context     2.3.0 | win32&#x2F;x86_64 | ocaml.5.3.0 | pinned(file:&#x2F;&#x2F;C:&#x2F;Users&#x2F;s&#x2F;topkg.1.0.8)
&lt;&#x2F;span&gt;&lt;span&gt;# path        ~\AppData\Local\opam\default\.opam-switch\build\topkg.1.0.8
&lt;&#x2F;span&gt;&lt;span&gt;# command     ~\AppData\Local\opam\default\bin\ocaml.exe pkg&#x2F;pkg.ml build --pkg-name topkg --dev-pkg true
&lt;&#x2F;span&gt;&lt;span&gt;# exit-code   125
&lt;&#x2F;span&gt;&lt;span&gt;# env-file    ~\AppData\Local\opam\log\topkg-6140-4d0e5a.env
&lt;&#x2F;span&gt;&lt;span&gt;# output-file ~\AppData\Local\opam\log\topkg-6140-4d0e5a.out
&lt;&#x2F;span&gt;&lt;span&gt;### output ###
&lt;&#x2F;span&gt;&lt;span&gt;# Exception: Fl_package_base.No_such_package (&amp;quot;findlib&amp;quot;, &amp;quot;&amp;quot;).
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt; Error report &amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;  🐫
&lt;&#x2F;span&gt;&lt;span&gt;┌─ The following actions failed
&lt;&#x2F;span&gt;&lt;span&gt;│ λ build topkg 1.0.8
&lt;&#x2F;span&gt;&lt;span&gt;└─
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Looks good, especially this line:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;# context     2.3.0 | win32&#x2F;x86_64 | ocaml.5.3.0 | pinned(file:&#x2F;&#x2F;C:&#x2F;Users&#x2F;s&#x2F;topkg.1.0.8)
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;To debug this problem it will help to have a command we can run to quickly
reproduce it. Running the &lt;code&gt;topkg&lt;&#x2F;code&gt;’s build command, copied from its opam file,
causes the same error as we see when installing &lt;code&gt;topkg&lt;&#x2F;code&gt; as a dependency:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;PS C:\Users\s\topkg.1.0.8&amp;gt; opam exec ocaml -- .\pkg\pkg.ml build --pkg-name topkg --dev-pkg true
&lt;&#x2F;span&gt;&lt;span&gt;Exception: Fl_package_base.No_such_package (&amp;quot;findlib&amp;quot;, &amp;quot;&amp;quot;).
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Before digging into the source of &lt;code&gt;topkg&lt;&#x2F;code&gt; too deeply it might pay to verify that
findlib is indeed installed:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;PS C:\Users\s\topkg.1.0.8&amp;gt; cat C:\Users\s\AppData\Local\opam\default\lib\findlib\META
&lt;&#x2F;span&gt;&lt;span&gt;# specifications for &amp;quot;findlib&amp;quot;:
&lt;&#x2F;span&gt;&lt;span&gt;description = &amp;quot;Package manager&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;requires = &amp;quot;findlib.internal&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;requires(toploop) += &amp;quot;findlib.top&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;requires(create_toploop) += &amp;quot;findlib.top&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;version = &amp;quot;1.9.8&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;package &amp;quot;internal&amp;quot; (
&lt;&#x2F;span&gt;&lt;span&gt;  version = &amp;quot;1.9.8&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;  description = &amp;quot;Package manager&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;  requires = &amp;quot;&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;  archive(byte) = &amp;quot;findlib.cma&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;  archive(native) = &amp;quot;findlib.cmxa&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;  plugin(byte) = &amp;quot;findlib.cma&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;  plugin(native) = &amp;quot;findlib.cmxs&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;)
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;package &amp;quot;dynload&amp;quot; (
&lt;&#x2F;span&gt;&lt;span&gt;  version = &amp;quot;1.9.8&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;  description = &amp;quot;Package manager dynamic loader&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;  requires = &amp;quot;findlib dynlink&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;  archive(byte) = &amp;quot;findlib_dynload.cma&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;  archive(native) = &amp;quot;findlib_dynload.cmxa&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;#Even if it strange and discouraged to dynload this package
&lt;&#x2F;span&gt;&lt;span&gt;  plugin(byte) = &amp;quot;findlib_dynload.cma&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;  plugin(native) = &amp;quot;findlib_dynload.cmxs&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;  linkopts = &amp;quot;-linkall&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;)
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;package &amp;quot;top&amp;quot; (
&lt;&#x2F;span&gt;&lt;span&gt;  version = &amp;quot;1.9.8&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;  description = &amp;quot;Package manager toplevel support&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;  requires = &amp;quot;findlib.internal&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;  archive(byte) = &amp;quot;findlib_top.cma&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;  archive(native) = &amp;quot;findlib_top.cmxa&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;)
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Ok this suggests that the problem is with &lt;code&gt;topkg&lt;&#x2F;code&gt; itself and not with my
environment. I tried installing &lt;code&gt;llama&lt;&#x2F;code&gt;’s dependencies with Opam on Linux and
&lt;code&gt;topkg&lt;&#x2F;code&gt; built fine, suggesting that the problem is specific to Windows. A likely
hypothesis is that it’s related to differences with how file paths work between
the Unix world and Windows, since this is a common point of pain for running
OCaml programs on Windows in my experience. The main two differences that tend
to cause problems are that the path separator is &lt;code&gt;\&lt;&#x2F;code&gt; on Windows and &lt;code&gt;&#x2F;&lt;&#x2F;code&gt; on Unix,
and that Windows paths begin with &lt;code&gt;C:&lt;&#x2F;code&gt; while &lt;code&gt;:&lt;&#x2F;code&gt; is conventionally used to
delimit multiple paths when they appear in strings on Unix systems (Windows
tends to use &lt;code&gt;;&lt;&#x2F;code&gt; for this instead to avoid confusion around the colon in &lt;code&gt;C:&lt;&#x2F;code&gt;).&lt;&#x2F;p&gt;
&lt;p&gt;Next step was to instrument &lt;code&gt;topkg&lt;&#x2F;code&gt; with debug printouts to better understand
where the error was happening, but pretty quickly I realized that the problem
was happening before any code in &lt;code&gt;topkg&lt;&#x2F;code&gt; could run.&lt;&#x2F;p&gt;
&lt;p&gt;Here’s a simplified version of &lt;code&gt;pkg&#x2F;pkg.ml&lt;&#x2F;code&gt; from the &lt;code&gt;topkg&lt;&#x2F;code&gt; package that still exhibits the problem:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;ocaml&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-ocaml &quot;&gt;&lt;code class=&quot;language-ocaml&quot; data-lang=&quot;ocaml&quot;&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;#!&#x2F;&lt;&#x2F;span&gt;&lt;span&gt;usr&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&#x2F;&lt;&#x2F;span&gt;&lt;span&gt;bin&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&#x2F;&lt;&#x2F;span&gt;&lt;span&gt;env ocaml
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;#use &lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;topfind&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;let &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;() &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; print_endline &lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;hi&amp;quot;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Now I can run:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;PS C:\Users\s\topkg.1.0.8&amp;gt; opam exec ocaml -- .\pkg\pkg.ml
&lt;&#x2F;span&gt;&lt;span&gt;Exception: Fl_package_base.No_such_package (&amp;quot;findlib&amp;quot;, &amp;quot;&amp;quot;).
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;And of course removing the &lt;code&gt;#use &quot;topfind&quot;&lt;&#x2F;code&gt; from the top of that file makes the
problem go away.&lt;&#x2F;p&gt;
&lt;p&gt;Interestingly I can add code before the &lt;code&gt;#use &quot;topfind&quot;&lt;&#x2F;code&gt; line and it still gets
executed:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;ocaml&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-ocaml &quot;&gt;&lt;code class=&quot;language-ocaml&quot; data-lang=&quot;ocaml&quot;&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;#!&#x2F;&lt;&#x2F;span&gt;&lt;span&gt;usr&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&#x2F;&lt;&#x2F;span&gt;&lt;span&gt;bin&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&#x2F;&lt;&#x2F;span&gt;&lt;span&gt;env ocaml
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;let &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;() &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; print_endline &lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;before&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;;;
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;#use &lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;topfind&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;let &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;() &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; print_endline &lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;after&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;;;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The output is:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;PS C:\Users\s\topkg.1.0.8&amp;gt; opam exec ocaml -- .\pkg\pkg.ml
&lt;&#x2F;span&gt;&lt;span&gt;before
&lt;&#x2F;span&gt;&lt;span&gt;Exception: Fl_package_base.No_such_package (&amp;quot;findlib&amp;quot;, &amp;quot;&amp;quot;).
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Searching online I found &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;ocaml-community&#x2F;utop&#x2F;issues&#x2F;112&quot;&gt;this
issue&lt;&#x2F;a&gt; which mentions the
&lt;code&gt;OCAML_TOPLEVEL_PATH&lt;&#x2F;code&gt; variable. On my Windows machine this variable is set to
&lt;code&gt;C:\Users\s\AppData\Local\opam\default\lib\toplevel&lt;&#x2F;code&gt;. That path refers to a
folder and inside that folder is a file named &lt;code&gt;topfind&lt;&#x2F;code&gt; with some OCaml code in
it. I’m not too familiar with the &lt;code&gt;#use&lt;&#x2F;code&gt; directive in OCaml but it seems to
behave similarly to &lt;code&gt;#include&lt;&#x2F;code&gt; in C; acting as though the contents of the
specified file replaced the occurrence of &lt;code&gt;#use &amp;lt;file&amp;gt;&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Some of the code in &lt;code&gt;topfind&lt;&#x2F;code&gt; makes calls to the &lt;code&gt;findlib&lt;&#x2F;code&gt; library (part of the
&lt;code&gt;ocamlfind&lt;&#x2F;code&gt; package) which is generally concerned with mapping library names to
files containing compiled OCaml modules. The &lt;code&gt;ocamlfind&lt;&#x2F;code&gt; package installs a file
named &lt;code&gt;findlib.conf&lt;&#x2F;code&gt; containing some metadata about library locations within an
Opam switch. On my Windows machine this file was at &lt;code&gt;C:\Users\s\AppData\Local\opam\default\lib\findlib.conf&lt;&#x2F;code&gt;
and its contents was:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;destdir=&amp;quot;C:\\Users\\s\\AppData\\Local\\opam\\default\\lib&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;path=&amp;quot;C:\\Users\\s\\AppData\\Local\\opam\\default&#x2F;lib&#x2F;ocaml:C:\\Users\\s\\AppData\\Local\\opam\\default\\lib&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;ocamlc=&amp;quot;ocamlc.opt&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;ocamlopt=&amp;quot;ocamlopt.opt&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;ocamldep=&amp;quot;ocamldep.opt&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;ocamldoc=&amp;quot;ocamldoc.opt&amp;quot;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The &lt;code&gt;path&lt;&#x2F;code&gt; field looks suspicious, since the paths on Windows begin with &lt;code&gt;C:&lt;&#x2F;code&gt;,
but it looks like a colon character is also being used to separate the two paths.
Recall that on Windows it’s common to use a semicolon to separate paths, rather than a colon
as is typically seen on Unix. As an experiment I manually replaced the colon
with a semicolon in &lt;code&gt;findlib.conf&lt;&#x2F;code&gt; and suddenly I could successfully build
&lt;code&gt;topkg&lt;&#x2F;code&gt; both on its own and as a dependency for &lt;code&gt;llama&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;PS C:\Users\s\llama&amp;gt; opam install . --deps-only
&lt;&#x2F;span&gt;&lt;span&gt;[llama_core.dev] synchronised (no changes)
&lt;&#x2F;span&gt;&lt;span&gt;[llama.dev] synchronised (no changes)
&lt;&#x2F;span&gt;&lt;span&gt;[llama_interactive.dev] synchronised (no changes)
&lt;&#x2F;span&gt;&lt;span&gt;[llama_midi.dev] synchronised (no changes)
&lt;&#x2F;span&gt;&lt;span&gt;[llama_tests.dev] synchronised (no changes)
&lt;&#x2F;span&gt;&lt;span&gt;The following actions will be performed:
&lt;&#x2F;span&gt;&lt;span&gt;=== install 2 packages
&lt;&#x2F;span&gt;&lt;span&gt;  ∗ topkg 1.0.8 (pinned) [required by tsdl]
&lt;&#x2F;span&gt;&lt;span&gt;  ∗ tsdl  1.1.0          [required by llama_interactive]
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;Proceed with ∗ 2 installations? [y&#x2F;n] y
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt; Processing actions &amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;  🐫
&lt;&#x2F;span&gt;&lt;span&gt;⬇ retrieved tsdl.1.1.0  (cached)
&lt;&#x2F;span&gt;&lt;span&gt;∗ installed topkg.1.0.8
&lt;&#x2F;span&gt;&lt;span&gt;∗ installed tsdl.1.1.0
&lt;&#x2F;span&gt;&lt;span&gt;Done.
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;So the problem wasn’t with &lt;code&gt;topkg&lt;&#x2F;code&gt; after all but rather with &lt;code&gt;ocamlfind&lt;&#x2F;code&gt;. The
&lt;code&gt;ocamlfind&lt;&#x2F;code&gt; package is a bit old-school, building with a &lt;code&gt;configure&lt;&#x2F;code&gt; script
and &lt;code&gt;Makefile&lt;&#x2F;code&gt;. I’m guessing somewhere in there it fails to detect that it’s
running on Windows, at least in Msys2 environments, and uses the wrong path
delimiter for paths in &lt;code&gt;findlib.conf&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;With that problem adequately worked around we should finally be able to run a
graphical example:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;PS C:\Users\s\llama&amp;gt; dune exec .\examples\interactive.exe
&lt;&#x2F;span&gt;&lt;span&gt;File &amp;quot;examples&#x2F;dune&amp;quot;, line 44, characters 14-25:
&lt;&#x2F;span&gt;&lt;span&gt;44 |  (public_name interactive)
&lt;&#x2F;span&gt;&lt;span&gt;                   ^^^^^^^^^^^
&lt;&#x2F;span&gt;&lt;span&gt;C:&#x2F;msys64&#x2F;mingw64&#x2F;bin&#x2F;..&#x2F;lib&#x2F;gcc&#x2F;x86_64-w64-mingw32&#x2F;14.2.0&#x2F;..&#x2F;..&#x2F;..&#x2F;..&#x2F;x86_64-w64-mingw32&#x2F;bin&#x2F;ld.exe: C:&#x2F;msys64&#x2F;mingw64&#x2F;bin&#x2F;..&#x2F;lib&#x2F;libmingw32.a(lib64_libmingw32_a-ucrtexewin.o): in function `wmain&amp;#39;:
&lt;&#x2F;span&gt;&lt;span&gt;C:&#x2F;M&#x2F;B&#x2F;src&#x2F;mingw-w64&#x2F;mingw-w64-crt&#x2F;crt&#x2F;crtexewin.c:67:(.text+0xb5): undefined reference to `wWinMain&amp;#39;
&lt;&#x2F;span&gt;&lt;span&gt;collect2.exe: error: ld returned 1 exit status
&lt;&#x2F;span&gt;&lt;span&gt;** Fatal error: Error during linking
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;File &amp;quot;caml_startup&amp;quot;, line 1:
&lt;&#x2F;span&gt;&lt;span&gt;Error: Error during linking (exit code 2)
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Not quite yet!&lt;&#x2F;p&gt;
&lt;h2 id=&quot;fixing-the-linker-error&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#fixing-the-linker-error&quot; aria-label=&quot;Anchor link for: fixing-the-linker-error&quot;&gt;Fixing the linker error&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;Linker errors like this usually mean that we’re not linking against a certain
shared library, in this case whichever shared library would define the symbol
&lt;code&gt;wWinMain&lt;&#x2F;code&gt;. I assumed the &lt;code&gt;tsdl&lt;&#x2F;code&gt; library would have taken care of making sure
the appropriate linker flags were passed. It works fine on Linux and MacOS but
maybe there are some Windows-specific flags it’s leaving out.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;code&gt;pkgconf&lt;&#x2F;code&gt; should be able to tell us what flags are necessary to use a certain
library. I assume whatever flags are necessary for SDL2 should suffice here:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;PS C:\Users\s\llama&amp;gt; pkgconf.exe sdl2 --libs
&lt;&#x2F;span&gt;&lt;span&gt;-LC:&#x2F;msys64&#x2F;mingw64&#x2F;bin&#x2F;..&#x2F;lib -lmingw32 -mwindows -lSDL2main -lSDL2
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;I’ll start by adding these to the &lt;code&gt;library_flags&lt;&#x2F;code&gt; field of the &lt;code&gt;executable&lt;&#x2F;code&gt;
stanza of the example I’m trying to run:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;dune&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-dune &quot;&gt;&lt;code class=&quot;language-dune&quot; data-lang=&quot;dune&quot;&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;executable
&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;public_name&lt;&#x2F;span&gt;&lt;span&gt; interactive)
&lt;&#x2F;span&gt;&lt;span&gt; (modules interactive)
&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;package&lt;&#x2F;span&gt;&lt;span&gt; llama_interactive)
&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;libraries&lt;&#x2F;span&gt;&lt;span&gt; llama_interactive keyboard_helper)
&lt;&#x2F;span&gt;&lt;span&gt; (link_flags -LC:&#x2F;msys64&#x2F;mingw64&#x2F;bin&#x2F;..&#x2F;lib -lmingw32 -mwindows -lSDL2main -lSDL2))
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Building the example:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;PS C:\Users\s\llama&amp;gt; dune build .\examples\interactive.exe
&lt;&#x2F;span&gt;&lt;span&gt;File &amp;quot;examples&#x2F;dune&amp;quot;, line 44, characters 14-25:
&lt;&#x2F;span&gt;&lt;span&gt;44 |  (public_name interactive)
&lt;&#x2F;span&gt;&lt;span&gt;                   ^^^^^^^^^^^
&lt;&#x2F;span&gt;&lt;span&gt;C:\Users\s\AppData\Local\opam\default\bin\ocamlopt.opt.exe: unknown option &amp;#39;-LC:&#x2F;msys64&#x2F;mingw64&#x2F;bin&#x2F;..&#x2F;lib&amp;#39;.
&lt;&#x2F;span&gt;&lt;span&gt;Usage: ocamlopt &amp;lt;options&amp;gt; &amp;lt;files&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;Try &amp;#39;ocamlopt --help&amp;#39; for more information.
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Turns out you can’t pass raw linker flags to the OCaml compiler like that.
In the
&lt;a href=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;frustrating-interactions-with-the-ocaml-ecosystem-while-developing-a-synthesizer-library&#x2F;#linking-against-os-specific-native-libraries-with-dune-is-hard&quot;&gt;past&lt;&#x2F;a&gt;
I’ve had success wrapping the linker flags in &lt;code&gt;-cclib &quot;&amp;lt;flags&amp;gt;&quot;&lt;&#x2F;code&gt; so I tried
that:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;dune&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-dune &quot;&gt;&lt;code class=&quot;language-dune&quot; data-lang=&quot;dune&quot;&gt;&lt;span&gt;(link_flags -cclib &lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;-LC:&#x2F;msys64&#x2F;mingw64&#x2F;bin&#x2F;..&#x2F;lib -lmingw32 -mwindows -lSDL2main -lSDL2&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;And the error message changed which is often a sign of progress:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;PS C:\Users\s\llama&amp;gt; dune build .\examples\interactive.exe
&lt;&#x2F;span&gt;&lt;span&gt;File &amp;quot;examples&#x2F;dune&amp;quot;, line 44, characters 14-25:
&lt;&#x2F;span&gt;&lt;span&gt;44 |  (public_name interactive)
&lt;&#x2F;span&gt;&lt;span&gt;                   ^^^^^^^^^^^
&lt;&#x2F;span&gt;&lt;span&gt;flexlink: unknown option &amp;#39;-mwindows&amp;#39;.
&lt;&#x2F;span&gt;&lt;span&gt;FlexDLL version 0.44
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;Usage:
&lt;&#x2F;span&gt;&lt;span&gt;  flexlink -o &amp;lt;result.dll&#x2F;exe&amp;gt; file1.obj file2.obj ... -- &amp;lt;extra linker arguments&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;  -o                  Choose the name of the output file
&lt;&#x2F;span&gt;&lt;span&gt;  -exe                Link the main program as an exe file
&lt;&#x2F;span&gt;&lt;span&gt;  -maindll            Link the main program as a dll file
&lt;&#x2F;span&gt;&lt;span&gt;  -noflexdllobj       Do not add the Flexdll runtime object (for exe)
&lt;&#x2F;span&gt;&lt;span&gt;  -noentry            Do not use the Flexdll entry point (for dll)
&lt;&#x2F;span&gt;&lt;span&gt;  -noexport           Do not export any symbol
&lt;&#x2F;span&gt;&lt;span&gt;  -norelrelocs        Ensure that no relative relocation is generated
&lt;&#x2F;span&gt;&lt;span&gt;  -base               Specify base address (Win64 only)
&lt;&#x2F;span&gt;&lt;span&gt;  -pthread            Pass -pthread to the linker
&lt;&#x2F;span&gt;&lt;span&gt;  -I &amp;lt;dir&amp;gt;            Add a directory where to search for files
&lt;&#x2F;span&gt;&lt;span&gt;  -L &amp;lt;dir&amp;gt;            Add a directory where to search for files
&lt;&#x2F;span&gt;&lt;span&gt;  -l &amp;lt;lib&amp;gt;            Library file
&lt;&#x2F;span&gt;&lt;span&gt;  -chain {msvc|msvc64|cygwin64|mingw|mingw64|gnat|gnat64|ld}
&lt;&#x2F;span&gt;&lt;span&gt;                      Choose which linker to use
&lt;&#x2F;span&gt;&lt;span&gt;  -use-linker &amp;lt;cmd&amp;gt;   Choose an alternative linker to use
&lt;&#x2F;span&gt;&lt;span&gt;  -use-mt &amp;lt;cmd&amp;gt;       Choose an alternative manifest tool to use
&lt;&#x2F;span&gt;&lt;span&gt;  -x64                (Deprecated)
&lt;&#x2F;span&gt;&lt;span&gt;  -defaultlib &amp;lt;obj&amp;gt;   External object (no export, no import)
&lt;&#x2F;span&gt;&lt;span&gt;  -save-temps         Do not delete intermediate files
&lt;&#x2F;span&gt;&lt;span&gt;  -implib             Do not delete the generated import library
&lt;&#x2F;span&gt;&lt;span&gt;  -outdef             Produce a def file with exported symbols
&lt;&#x2F;span&gt;&lt;span&gt;  -v                  Increment verbosity (can be repeated)
&lt;&#x2F;span&gt;&lt;span&gt;  -show-exports       Show exported symbols
&lt;&#x2F;span&gt;&lt;span&gt;  -show-imports       Show imported symbols
&lt;&#x2F;span&gt;&lt;span&gt;  -dry                Show the linker command line, do not actually run it
&lt;&#x2F;span&gt;&lt;span&gt;  -dump               Only dump the content of object files
&lt;&#x2F;span&gt;&lt;span&gt;  -patch              Only patch the target image (to be used with -stack)
&lt;&#x2F;span&gt;&lt;span&gt;  -nocygpath          Do not use cygpath (default for msvc, mingw)
&lt;&#x2F;span&gt;&lt;span&gt;  -cygpath            Use cygpath (default for cygwin)
&lt;&#x2F;span&gt;&lt;span&gt;  -no-merge-manifest  Do not merge the manifest (takes precedence over -merge-manifest)
&lt;&#x2F;span&gt;&lt;span&gt;  -merge-manifest     Merge manifest to the dll or exe (if generated)
&lt;&#x2F;span&gt;&lt;span&gt;  -real-manifest      Use the generated manifest (default behavior)
&lt;&#x2F;span&gt;&lt;span&gt;  -default-manifest   Use the default manifest (default.manifest&#x2F;default_amd64.manifest)
&lt;&#x2F;span&gt;&lt;span&gt;  -export &amp;lt;sym&amp;gt;       Explicitly export a symbol
&lt;&#x2F;span&gt;&lt;span&gt;  -noreexport         Do not reexport symbols imported from import libraries
&lt;&#x2F;span&gt;&lt;span&gt;  -where              Show the FlexDLL directory
&lt;&#x2F;span&gt;&lt;span&gt;  -nounderscore       Normal symbols are not prefixed with an underscore
&lt;&#x2F;span&gt;&lt;span&gt;  -nodefaultlibs      Do not assume any default library
&lt;&#x2F;span&gt;&lt;span&gt;  -builtin            Use built-in linker to produce a dll
&lt;&#x2F;span&gt;&lt;span&gt;  -explain            Explain why library objects are linked
&lt;&#x2F;span&gt;&lt;span&gt;  -subsystem &amp;lt;id&amp;gt;     Set the subsystem (default: console)
&lt;&#x2F;span&gt;&lt;span&gt;  -custom-crt         Use a custom CRT
&lt;&#x2F;span&gt;&lt;span&gt;  -stack &amp;lt;int&amp;gt;        Set the stack reserve in the resulting image
&lt;&#x2F;span&gt;&lt;span&gt;  -link &amp;lt;option&amp;gt;      Next argument is passed verbatim to the linker
&lt;&#x2F;span&gt;&lt;span&gt;  -g                  (Ignored)
&lt;&#x2F;span&gt;&lt;span&gt;  -D &amp;lt;symbol&amp;gt;         (Ignored)
&lt;&#x2F;span&gt;&lt;span&gt;  -U &amp;lt;symbol&amp;gt;         (Ignored)
&lt;&#x2F;span&gt;&lt;span&gt;  --                  Following arguments are passed verbatim to the linker
&lt;&#x2F;span&gt;&lt;span&gt;  -version            Print linker version and FlexDLL directory and exit
&lt;&#x2F;span&gt;&lt;span&gt;  -vnum               Print linker version number and exit
&lt;&#x2F;span&gt;&lt;span&gt;  -help               Display this list of options
&lt;&#x2F;span&gt;&lt;span&gt;  --help              Display this list of options
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;Notes:
&lt;&#x2F;span&gt;&lt;span&gt;* The -I, -l and -L options do not need to be separated from their argument.
&lt;&#x2F;span&gt;&lt;span&gt;* An option like &#x2F;linkXXX is an abbrevation for &amp;#39;-link XXX&amp;#39;.
&lt;&#x2F;span&gt;&lt;span&gt;* An option like -Wl,-XXX is an abbreviation for &amp;#39;-link -XXX&amp;#39;.
&lt;&#x2F;span&gt;&lt;span&gt;* FlexDLL&amp;#39;s object files are searched by default in the same directory as
&lt;&#x2F;span&gt;&lt;span&gt;  flexlink, or in the directory given by the environment variable FLEXDIR
&lt;&#x2F;span&gt;&lt;span&gt;  if it is defined.
&lt;&#x2F;span&gt;&lt;span&gt;* Extra argument can be passed in the environment variable FLEXLINKFLAGS.
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;Homepage: https:&#x2F;&#x2F;github.com&#x2F;ocaml&#x2F;flexdll
&lt;&#x2F;span&gt;&lt;span&gt;File &amp;quot;caml_startup&amp;quot;, line 1:
&lt;&#x2F;span&gt;&lt;span&gt;Error: Error during linking (exit code 2)
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Getting rid of the &lt;code&gt;-mwindows&lt;&#x2F;code&gt; argument just gets us back to the original linker
error, so I assume it’s important.&lt;&#x2F;p&gt;
&lt;p&gt;Reading more about &lt;code&gt;wMinMain&lt;&#x2F;code&gt; and it looks like I was wrong assuming that we
need to pass additional linker flags to tell the linker which shared library to
look in. Rather, the linker was looking for &lt;code&gt;wWinMain&lt;&#x2F;code&gt; as the entry point to the
program called by the C runtime’s initialization code. I have to define it
myself. Perhaps OCaml assumes that the entry point will be named &lt;code&gt;WinMain&lt;&#x2F;code&gt; which
seems to be the entry point for Windows C programs that don’t accept unicode
arguments whereas &lt;code&gt;wWinMain&lt;&#x2F;code&gt; is used when programs &lt;em&gt;do&lt;&#x2F;em&gt; accept unicode
arguments (I think the “w” stands for “wide” as in “wide character” since
unicode characters can be wider than a byte). For some reason depending on SDL
seems to require that the entry point be &lt;code&gt;wWinMain&lt;&#x2F;code&gt;. I don’t understand why.&lt;&#x2F;p&gt;
&lt;p&gt;Following &lt;a href=&quot;https:&#x2F;&#x2F;dune.readthedocs.io&#x2F;en&#x2F;latest&#x2F;howto&#x2F;override-default-entrypoint.html&quot;&gt;these
instructions&lt;&#x2F;a&gt;
I defined a custom entry point in some C code.
For starters it’s stolen from &lt;a href=&quot;https:&#x2F;&#x2F;sourceforge.net&#x2F;p&#x2F;mingw-w64&#x2F;wiki2&#x2F;Unicode%20apps&#x2F;&quot;&gt;here&lt;&#x2F;a&gt;:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;c&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-c &quot;&gt;&lt;code class=&quot;language-c&quot; data-lang=&quot;c&quot;&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;#include &lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;lt;wchar.h&amp;gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;#include &lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;lt;stdio.h&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;int
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;wWinMain &lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;int &lt;&#x2F;span&gt;&lt;span&gt;argc, wchar_t &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;**&lt;&#x2F;span&gt;&lt;span&gt;argv)
&lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;wprintf&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;L&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;Hello&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;\n&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;);
&lt;&#x2F;span&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;return &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt;;
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;And in the &lt;code&gt;dune&lt;&#x2F;code&gt; file:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;dune&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-dune &quot;&gt;&lt;code class=&quot;language-dune&quot; data-lang=&quot;dune&quot;&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;executable
&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;public_name&lt;&#x2F;span&gt;&lt;span&gt; interactive)
&lt;&#x2F;span&gt;&lt;span&gt; (modules interactive)
&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;package&lt;&#x2F;span&gt;&lt;span&gt; llama_interactive)
&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;libraries&lt;&#x2F;span&gt;&lt;span&gt; llama_interactive keyboard_helper)
&lt;&#x2F;span&gt;&lt;span&gt; (foreign_stubs
&lt;&#x2F;span&gt;&lt;span&gt;  (language c)
&lt;&#x2F;span&gt;&lt;span&gt;  (names interactive_win)))
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Testing it out:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;PS C:\Users\s\llama&amp;gt; dune exec .\examples\interactive.exe
&lt;&#x2F;span&gt;&lt;span&gt;Done: 49% (96&#x2F;194, 98 left) (jobs: 0)Hello
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Nice, the linker error is gone. Obviously this just prints “Hello” and exits
without actually calling into any OCaml code. Now I just need to change the C
code to call into the original OCaml code:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;c&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-c &quot;&gt;&lt;code class=&quot;language-c&quot; data-lang=&quot;c&quot;&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;#include &lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;lt;wchar.h&amp;gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;#include &lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;lt;stdio.h&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;#define &lt;&#x2F;span&gt;&lt;span&gt;CAML_INTERNALS
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;#include &lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;caml&#x2F;misc.h&amp;quot;
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;#include &lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;caml&#x2F;mlvalues.h&amp;quot;
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;#include &lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;caml&#x2F;sys.h&amp;quot;
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;#include &lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;caml&#x2F;callback.h&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;int
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;wWinMain &lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;int &lt;&#x2F;span&gt;&lt;span&gt;argc, wchar_t &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;**&lt;&#x2F;span&gt;&lt;span&gt;argv)
&lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;  caml_main(argv);
&lt;&#x2F;span&gt;&lt;span&gt;  caml_do_exit(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt;);
&lt;&#x2F;span&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;return &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt;;
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;But then when I ran the example nothing happened. Adding print statements to the
OCaml code didn’t have any effect, and it looks like the program is exiting
somewhere inside &lt;code&gt;caml_main&lt;&#x2F;code&gt; with no error message.&lt;&#x2F;p&gt;
&lt;p&gt;Powershell provides exit codes of programs in the &lt;code&gt;$LastExitCode&lt;&#x2F;code&gt; variable:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;PS C:\Users\s\llama&amp;gt; dune exec .\examples\interactive.exe
&lt;&#x2F;span&gt;&lt;span&gt;Done: 100% (194&#x2F;194, 0 left) (jobs: 0)
&lt;&#x2F;span&gt;&lt;span&gt;PS C:\Users\s\llama&amp;gt; $LastExitCode
&lt;&#x2F;span&gt;&lt;span&gt;-1073741819
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Converting to a 32-bit unsigned int assuming 2’s complement this is &lt;code&gt;0xC0000005&lt;&#x2F;code&gt;
which corresponds to an “Access Violation” which is similar to a segmentation
fault in the Unix world, so probably I’m doing something wrong with pointers.&lt;&#x2F;p&gt;
&lt;p&gt;For inspiration I took a look at the real OCaml &lt;code&gt;main&lt;&#x2F;code&gt; function defined
&lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;ocaml&#x2F;ocaml&#x2F;blob&#x2F;trunk&#x2F;runtime&#x2F;main.c&quot;&gt;here&lt;&#x2F;a&gt;.
There’s some Windows-specific parts that I was missing which I added.
I also realized I was using the wrong type for my &lt;code&gt;wWinMain&lt;&#x2F;code&gt; function so I
changed its arguments based on some Microsoft documentation I found. This meant
I needed to convert the Windows way of representing command-line arguments into
the Unix-style &lt;code&gt;argv&lt;&#x2F;code&gt; which OCaml presumably expects. I was running out of time so
instead I just generated a fake value for &lt;code&gt;argv&lt;&#x2F;code&gt;. Here’s the C program I ended up with:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;C&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-C &quot;&gt;&lt;code class=&quot;language-C&quot; data-lang=&quot;C&quot;&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;#include &lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;lt;wchar.h&amp;gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;#include &lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;lt;stdio.h&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;#define &lt;&#x2F;span&gt;&lt;span&gt;CAML_INTERNALS
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;#include &lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;caml&#x2F;misc.h&amp;quot;
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;#include &lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;caml&#x2F;mlvalues.h&amp;quot;
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;#include &lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;caml&#x2F;sys.h&amp;quot;
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;#include &lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;caml&#x2F;osdeps.h&amp;quot;
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;#include &lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;caml&#x2F;callback.h&amp;quot;
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;#include &lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;lt;windows.h&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;int &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;WINAPI &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;wWinMain&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;HINSTANCE &lt;&#x2F;span&gt;&lt;span&gt;hInstance, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;HINSTANCE &lt;&#x2F;span&gt;&lt;span&gt;hPrevInstance, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;PWSTR &lt;&#x2F;span&gt;&lt;span&gt;pCmdLine, &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;int &lt;&#x2F;span&gt;&lt;span&gt;nCmdShow) {
&lt;&#x2F;span&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; fake argv for now since I don&amp;#39;t know how to convert windows arguments to argv
&lt;&#x2F;span&gt;&lt;span&gt;  char_os argv0[] &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span&gt;{ (&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;unsigned short int&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;#39;x&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;0 &lt;&#x2F;span&gt;&lt;span&gt;};
&lt;&#x2F;span&gt;&lt;span&gt;  char_os&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;**&lt;&#x2F;span&gt;&lt;span&gt; argv &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;malloc&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;sizeof&lt;&#x2F;span&gt;&lt;span&gt;(char_os&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;*&lt;&#x2F;span&gt;&lt;span&gt;) &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;* &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;2&lt;&#x2F;span&gt;&lt;span&gt;);
&lt;&#x2F;span&gt;&lt;span&gt;  argv[&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt;] &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; argv0;
&lt;&#x2F;span&gt;&lt;span&gt;  argv[&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;] &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;NULL&lt;&#x2F;span&gt;&lt;span&gt;; &lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; ocaml probably uses a null terminator for its argv since there&amp;#39;s no argc?
&lt;&#x2F;span&gt;&lt;span&gt;  caml_main(argv);
&lt;&#x2F;span&gt;&lt;span&gt;  caml_do_exit(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt;);
&lt;&#x2F;span&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;return &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt;;
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;And finally it works!&lt;&#x2F;p&gt;
&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https:&#x2F;&#x2F;www.youtube.com&#x2F;embed&#x2F;vS2qgKDK7y4?si=FNlN2OBef58aj_Nw&quot; title=&quot;YouTube video player&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&quot; referrerpolicy=&quot;strict-origin-when-cross-origin&quot; allowfullscreen&gt;&lt;&#x2F;iframe&gt;
&lt;p&gt;Well it mostly works. There’s about a second of input latency which isn’t
there when I run it on Linux or MacOS, but at least it can now open a window and
get input from the keyboard and mouse.&lt;&#x2F;p&gt;
&lt;p&gt;That brings us to the end of the 2-day period I’ve allocated for working on
this project. Clearly &lt;code&gt;llama&lt;&#x2F;code&gt; is not ready for use in “production” on Windows but at
least I now know that it’s technically possible to make it work. All the
problems I worked around the past 2 days are not problems with &lt;code&gt;llama&lt;&#x2F;code&gt; itself
but various other packages in the OCaml ecosystem (at least &lt;code&gt;conf-ao&lt;&#x2F;code&gt; calling the wrong
&lt;code&gt;pkg-config&lt;&#x2F;code&gt; executable and missing Msys2 &lt;code&gt;depexts&lt;&#x2F;code&gt; and &lt;code&gt;ocamlfind&lt;&#x2F;code&gt; using the
wrong path delimiters on Windows). I’ll gradually work on upstreaming fixes to
these problems but in the meantime I’ll be playing my synthesizer on Unix
machines only.&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Scope Creep</title>
          <pubDate>Fri, 09 May 2025 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/scope-creep/</link>
          <guid>https://www.gridbugs.org/scope-creep/</guid>
          <description xml:base="https://www.gridbugs.org/scope-creep/">&lt;p&gt;&lt;a href=&quot;https:&#x2F;&#x2F;gridbugs.itch.io&#x2F;scope-creep&quot;&gt;Scope Creep&lt;&#x2F;a&gt; is a short first-person
horror game rendered with a virtual oscilloscope. In the game the player explores a
dungeon collecting orbs to open a door and escape. Each time an orb is
collected a new class of enemies spawn. All the graphics including text and
enemy sprites is rendered by using a &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;gridbugs&#x2F;caw&quot;&gt;synthesizer
library&lt;&#x2F;a&gt; to generating a stereo audio signal
which is then visualized with a virtual XY oscilloscope to produce the game’s
graphics. The audio signal also plays out of the speakers so in theory the game
could be rendered on a hardware XY oscilloscope.&lt;&#x2F;p&gt;
&lt;p&gt;It’s my 10th (what!) 7DRL game jam entry.
Play it in a web browser on &lt;a href=&quot;https:&#x2F;&#x2F;gridbugs.itch.io&#x2F;scope-creep&quot;&gt;its itch.io page&lt;&#x2F;a&gt;.
The source code is &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;gridbugs&#x2F;scope-creep&quot;&gt;here&lt;&#x2F;a&gt;.
And the devlog I wrote while developing this game begins &lt;a href=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;7drl2025-day1&#x2F;&quot;&gt;here&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https:&#x2F;&#x2F;www.youtube.com&#x2F;embed&#x2F;Uc4z52vJHkA?si=-b49jlildnsSHXGd&quot; title=&quot;YouTube video player&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&quot; referrerpolicy=&quot;strict-origin-when-cross-origin&quot; allowfullscreen&gt;&lt;&#x2F;iframe&gt;
&lt;p&gt;I’ve been developing a software-defined modular synthesizer library for a
couple of years. Its current incarnation is the &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;gridbugs&#x2F;caw&quot;&gt;Combinatorial Audio
Workstation (CAW)&lt;&#x2F;a&gt; - a Rust library for declaratively building synthesizers.
I used it to generate the audio signals used in Scope Creep, as well as for procedurally-generating the
music in real time for a previous project &lt;a href=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;electric-organ&#x2F;&quot;&gt;Electric
Organ&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;I came across &lt;a href=&quot;https:&#x2F;&#x2F;oscilloscopemusic.com&#x2F;watch&#x2F;n-spheres&quot;&gt;Jerobeam Fenderson’s oscilloscope
music&lt;&#x2F;a&gt; which inspired me to add
a stereo oscillographics visualizer to CAW (&lt;a href=&quot;https:&#x2F;&#x2F;gridbugs.github.io&#x2F;bevy-caw-experiment&#x2F;&quot;&gt;here’s a
demo!&lt;&#x2F;a&gt;) and then a friend
suggested I use this technique to render a first-person game.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;scope-creep&#x2F;screenshot1.png&quot; alt=&quot;3d rendered scene with several enemies&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-renderer&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#the-renderer&quot; aria-label=&quot;Anchor link for: the-renderer&quot;&gt;The Renderer&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;Rendering with an oscilloscope means we have to get creative with the rendering
algorithm. Most 3D renderers use a pixel-based “raster” algorithm, where each polygon
is converted to a collection of pixels that end up on the screen unless some
other polygon’s pixels occlude them. The order in which polygons are drawn
doesn’t matter because the renderer keeps track of depth of each visible pixel
and only draws new pixels at the same point on the screen if they represent a
point in space closer to the eye than what’s currently drawn there. This is
called “depth buffering”.&lt;&#x2F;p&gt;
&lt;p&gt;Depth buffering wasn’t an option for this project because the only thing the
render can draw is lines - not pixels. Only the outline of each wall is drawn.
The black pixels making up the floor, ceiling, and wall surfaces are drawn once
at the start of each frame when the screen is cleared, and not updated again.
This means that we can’t draw black surfaces to occlude whatever would be
behind them. Instead we need to avoid drawing anything that would be occluded
in the first place.&lt;&#x2F;p&gt;
&lt;p&gt;So how do we figure out which parts of which walls are visible? I’ll give a
rough sketch of the approach taken by Scope Creep. It won’t be a complete
explanation of the renderer - just the key idea it’s based on. For more
details,
&lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;gridbugs&#x2F;scope-creep&#x2F;blob&#x2F;b5f6bafacb52e9ecbc1332ffbcde1a60873e55e4&#x2F;src&#x2F;main.rs#L1412&quot;&gt;here&lt;&#x2F;a&gt;’s
a link to where this algorithm is implemented (but beware that this code is
from a game jam!).&lt;&#x2F;p&gt;
&lt;p&gt;Here’s a typical 3D scene. All the walls are the same height, and the floor and
ceiling are completely flat. It happens that all corners are right-angles but
this is not necessary for this rendering technique to work (just a limitation
of the terrain generation algorithm I’m using here).&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;scope-creep&#x2F;debug1.png&quot; alt=&quot;&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;A couple of observations about this scene:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;The scene is symmetrical along the horizontal axis. For simplicity, I’ll therefore only talk about the top half of the scene from now on.&lt;&#x2F;li&gt;
&lt;li&gt;The scene is made up entirely of a collection of vertical lines, with each adjacent pair of vertical lines being connected by a “connecting” line.&lt;&#x2F;li&gt;
&lt;li&gt;Sometimes these connecting lines attach to the very top of a vertical line, and other times they connect somewhere in the middle.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;So to render a scene all we have to do is identify the vertical lines that make
it up, and for each vertical line, determine the height of the connecting line
attached to its left and right side.&lt;&#x2F;p&gt;
&lt;p&gt;There are three cases for how connecting lines attach to a vertical line. Either both connecting lines attach to the top:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;scope-creep&#x2F;debug1.1.png&quot; alt=&quot;&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;…or the connecting line on the left side attaches to the top but the connecting line on the right side does not:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;scope-creep&#x2F;debug1.2.png&quot; alt=&quot;&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;And for completeness, here’s one that’s only connected at the top on its right side:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;scope-creep&#x2F;debug1.3.png&quot; alt=&quot;&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Now let’s consider the scene from above.
Here’s the rendered scene again for convenience:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;scope-creep&#x2F;debug1.png&quot; alt=&quot;&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;…and here’s a bird’s eye view, with the open space in black and the walls in
bright yellow (the inaccessible parts of the map are shaded dark yellow). Each
corner visible in the 3D scene has been circled. The eye is the red circle at
the bottom of the map, and is facing upwards.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;scope-creep&#x2F;debug3.png&quot; alt=&quot;&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Manually highlighting the visible area gives a hint as to how to determine the
connectedness of vertical lines.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;scope-creep&#x2F;debug4.png&quot; alt=&quot;&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;The 3D scene again:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;scope-creep&#x2F;debug1.png&quot; alt=&quot;&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Sometimes when a corner between two walls is visible, both of the walls the
corner is between are visible too. Imagine shooting a ray of light from the eye
to the corner. If both sides of the corner are visible, the ray hits a solid
surface and stops.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;scope-creep&#x2F;debug5.png&quot; alt=&quot;&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;This corresponds to a vertical line with both its connecting lines connecting directly to its top.
The height of the vertical line is inversely proportional to how far the ray
travelled before it stopped (ie. the distance from the eye to the corner).&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;scope-creep&#x2F;debug8.png&quot; alt=&quot;&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;But if only one wall attached to the corner is visible, the ray continues until
it hits a wall somewhere behind the corner.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;scope-creep&#x2F;debug6.png&quot; alt=&quot;&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;And this corresponds to a vertical line with only one of its connecting lines
connected to the top, and the other connected part way down.
The point on the vertical line where the second connecting line connects is at
a distance from the centre of the vertical line inversely proportional to the
distance the ray travelled before it stopped.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;scope-creep&#x2F;debug7.png&quot; alt=&quot;&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;In addition to the level geometry Scope Creep also has items and enemies.
These need to be rendered in such a way that they are
occluded by walls. This is achieved by creating a new wall segment for each
enemy&#x2F;object, determining how much of that wall would be visible, and then
restricting the visible area while rendering the enemy&#x2F;object to that range.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;scope-creep&#x2F;screenshot2.png&quot; alt=&quot;&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;That’s the rough idea behind rendering in Scope Creep. Once all the vertical
and connecting lines have been computed, the sequence of movements in the X and
Y axis required to draw them as if with an etch-a-sketch is determined, and
this determines audio signal in the left and right channel respectively.&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>7 Day Roguelike 2025: Success!</title>
          <pubDate>Fri, 07 Mar 2025 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/7drl2025-day7/</link>
          <guid>https://www.gridbugs.org/7drl2025-day7/</guid>
          <description xml:base="https://www.gridbugs.org/7drl2025-day7/">&lt;p&gt;The game is finished. &lt;a href=&quot;https:&#x2F;&#x2F;gridbugs.itch.io&#x2F;scope-creep&quot;&gt;Play it on itch.io.&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;p&gt;I made a bunch of changes tonight. Notably, I removed the HUD and mana. I was
originally going to add upgrades and spells, but I decided that giving the
player a bunch of abilities will detract from the feeling of dread I want them
to feel while playing Scope Creep. Instead, the player remains week while the
dungeon gets progressively more dangerous each time they collect an orb.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;7drl2025-day7&#x2F;screenshot1.png&quot; alt=&quot;3d rendered scene with several enemies&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;I added two new types of enemy. After collecting the second orb, the game
spawns a bunch of weeping-angel-style enemies which move faster the less
directly you look at them, and move very fast while off-screen. After
collecting the final orb, the game spawns the “Ghost King” right behind you.
It’s fast, can move through walls, and is the only enemy that can chase you
into the hub area.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;7drl2025-day7&#x2F;screenshot3.png&quot; alt=&quot;scene with large enemy down a hallway&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;I spent a few hours playtesting and recording video and cut together &lt;a href=&quot;https:&#x2F;&#x2F;www.youtube.com&#x2F;watch?v=Uc4z52vJHkA&quot;&gt;a trailer&lt;&#x2F;a&gt;.
I made some balancing adjustments, took a bunch of screenshots for the itch.io page, and submitted the game.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;7drl2025-day7&#x2F;screenshot2.png&quot; alt=&quot;in-game map&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;I plan on writing another post or two about the algorithms that I came up with
for rendering Scope Creep. I doubt I’m the first to design a completely
vector-based renderer for Wolfenstein-3D-esque faux 3D graphics but it’s
certainly not a very commonly-used technique. It’s definitely not the first
game to run on an oscilloscope, though it might be the first first-person game?&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;7drl2025-day7&#x2F;screenshot4.png&quot; alt=&quot;two humanoid enemies in the foreground with smaller slug-like enemies in the background&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;And I should really back up my claim about being able to render Scope Creep on
a real 2D analog oscilloscope!&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>7 Day Roguelike 2025: Putting it all together</title>
          <pubDate>Thu, 06 Mar 2025 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/7drl2025-day6/</link>
          <guid>https://www.gridbugs.org/7drl2025-day6/</guid>
          <description xml:base="https://www.gridbugs.org/7drl2025-day6/">&lt;p&gt;After tonight I have the full game loop working. The dungeons are populated
with items and enemies, and when you collect all the artifacts and return to
the hub, the room starts shaking and a door opens that leads to a corridor with
“The End” written at the end. The bones are all there, and I have one more day
to polish and add content.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;7drl2025-day6&#x2F;screenshot.png&quot; alt=&quot;Screenshot showing a 3d rendered scene with several enemies&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;The three areas I’ll focus on tomorrow will be:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Adding upgrades including spells to help evade and survive enemies.&lt;&#x2F;li&gt;
&lt;li&gt;More enemy types. I’d like 2 more types of enemy, with a new enemy type
spawning each time you collect an artifact.&lt;&#x2F;li&gt;
&lt;li&gt;More variety of dungeons. Ideally each dungeon would be generated by a
distinct algorithm.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
</description>
      </item>
      <item>
          <title>7 Day Roguelike 2025: Map, Pickups</title>
          <pubDate>Wed, 05 Mar 2025 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/7drl2025-day5/</link>
          <guid>https://www.gridbugs.org/7drl2025-day5/</guid>
          <description xml:base="https://www.gridbugs.org/7drl2025-day5/">&lt;p&gt;Tonight I added a map mode (still rendered with oscillographics of course). It
only shows walls you have already seen.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;7drl2025-day5&#x2F;map.png&quot; alt=&quot;Top-down map of a dungeon, rendered with oscillographics&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;I also added some basic items for restoring health and mana.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;7drl2025-day5&#x2F;pickups.png&quot; alt=&quot;3d scene showing a star and heart-shaped pickups on the ground&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;When you walk into an enemy you now take damage rather than instantly dying,
and there’s an animation that plays where the image gets fuzzy for a second
after taking damage. The player has a period of invulnerability after taking
damage so they don’t immediately take more damage from the same source.&lt;&#x2F;p&gt;
&lt;p&gt;I also spent several hours tracking down a bug where some corners would appear to pop inside out when viewed from certain angles. A scene like this:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;7drl2025-day5&#x2F;good.png&quot; alt=&quot;3d scene where everything looks fine&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;…would appear like this:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;7drl2025-day5&#x2F;bad.png&quot; alt=&quot;The sceen from above but one of the corners appears inside out&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;The problem was related to how geometry is split so that the only walls
considered for rendering are those in front of the player, and walls with
components both in front of and behind the player are clipped so just the
portion in front of the player remains. Under some conditions a connected
series of walls would be separated such that the engine sees some corners as
two ends of two disconnected walls rather than the corner between a pair of
connected walls, which violated some other assumptions made elsewhere in the
engine resulting in inside-out looking walls.&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>7 Day Roguelike 2025: Text, Item Labels, Central Hub</title>
          <pubDate>Tue, 04 Mar 2025 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/7drl2025-day4/</link>
          <guid>https://www.gridbugs.org/7drl2025-day4/</guid>
          <description xml:base="https://www.gridbugs.org/7drl2025-day4/">&lt;p&gt;I added text to my engine to enable rendering a HUD and labels on items. This
required describing each uppercase letter, digit, and some other symbols, as a list of
coordinates. Like everything else in the game, text is rendered with
oscillographics. I also added a couple of new oscillographic patterns which I’m
using to represent the artifacts the player needs to collect. The health
display in the HUD is currently just a placeholder - there’s no damage system
yet.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;7drl2025-day4&#x2F;screenshot.png&quot; alt=&quot;Screenshot of a 3d-rendered scene rendered with oscillographics. Three label items are visible, with labels “ARTIFACT OF CHAOS”, “ARTIFACT OF HARMONY”, and “ARTIFACT OF ORDER”.&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;I also extended the level generator to generate three dungeons connected by a central hub.&lt;&#x2F;p&gt;
&lt;p&gt;Next up I’ll implement the mechanic of collecting the artifacts, add a final
door to the central hub that opens once each artifact has been collected, and
add some end text when the player walks through the door. After that the basic
skeleton of the game will be done, and I’ll spend the remainder of the jam
adding more content and mechanics.&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>7 Day Roguelike 2025: Collision Detection and Enemies</title>
          <pubDate>Mon, 03 Mar 2025 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/7drl2025-day3/</link>
          <guid>https://www.gridbugs.org/7drl2025-day3/</guid>
          <description xml:base="https://www.gridbugs.org/7drl2025-day3/">&lt;p&gt;I added a quick and dirty implementation of collision handling for the player
character. Then I added some enemy types: a ghost and a slug. The ghost can
walk through walls and causes the image to become noisy the closer it gets to
you. The slug can’t walk through walls and moves faster. Both are rendered with oscillographics.&lt;&#x2F;p&gt;
&lt;p&gt;Here’s how the ghost looks:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;7drl2025-day3&#x2F;screenshot.png&quot; alt=&quot;Screenshot of a 3d-rendered scene rendered with oscillographics. There is a vaguely humanoid shape in the foreground.&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;I also added a death screen simulating how a CRT looks when it switches off
with the image collapsing to a horizontal line, then to a dot. For now the
player dies if they touch an enemy.&lt;&#x2F;p&gt;
&lt;p&gt;I want to add a couple more enemies including one that only moves while you’re
not looking at it, weeping angel style.&lt;&#x2F;p&gt;
&lt;p&gt;In terms of gameplay, the game is mostly going to be about avoiding different
types of enemies as you navigate a dungeon and collect items. Rather than a
vertical dungeon I’m thinking there’ll be a central hub with passages leading
to three distinct dungeons. The player will need to explore each dungeon and
collect an artifact from each one. There are some pretty oscillographic
patterns that I want to include in the game, and these can be the different
artifacts.&lt;&#x2F;p&gt;
&lt;p&gt;A central hub serves several purposes, gameplay wise. Firstly I’m trying to
make the game feel scary. A relatively safe central hub allows players brief
periods of calm in between more stressful moments in the dungeons. The contrast
will help make the dungeon sections feel more intense. The second purpose is
that it will allow the final goal of the game - gather the artifacts to open
the door - be visible from the start, in the form of the locked door.&lt;&#x2F;p&gt;
&lt;p&gt;Finally a central hub means that I don’t have to worry about how to communicate
to the player that they are going down stairs. I can’t display text, and the
renderer can only display vertical walls and “cardboard cutout” images which
I’m using for enemies and items. I can’t think of an elegant way to display
stairs or similar within these constraints.&lt;&#x2F;p&gt;
&lt;p&gt;Once you gather all the artifacts, a door will open and then on the other side
is either the end of the game or a boss fight if I get time.&lt;&#x2F;p&gt;
&lt;p&gt;Maybe I’ll also include some spells or something so the game can have some
character progression and to add another dimension to evading the enemies or
maybe even killing the enemies.&lt;&#x2F;p&gt;
&lt;p&gt;Another stretch goal is rendering text so the game can display some flavour
text or UI. This is a prerequisite for adding spells or increasing the player’s
health above 1 as otherwise it will be hard to display info about available
spells or hit points.&lt;&#x2F;p&gt;
&lt;p&gt;So, the plan for the rest of the week, roughly in order, is:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;Generate the full map with three dungeons connected to a central hub.&lt;&#x2F;li&gt;
&lt;li&gt;Add artifacts in each dungeon and make it so the game detects when an
artifact is returned to the hub.&lt;&#x2F;li&gt;
&lt;li&gt;Add a locked door that opens when all the artifacts have been gathered. At
first, walking through the door ends the game and winning the game is
implied by this.&lt;&#x2F;li&gt;
&lt;li&gt;Text rendering.&lt;&#x2F;li&gt;
&lt;li&gt;Display a message hinting what the player needs to do (gather 3 artifacts to open the door).&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;Then depending on how much time I have left and how the game feels I’ll decide
whether to add new mechanics like player upgrades, spells, a boss fight, etc.&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>7 Day Roguelike 2025: Oscillographics</title>
          <pubDate>Sun, 02 Mar 2025 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/7drl2025-day2/</link>
          <guid>https://www.gridbugs.org/7drl2025-day2/</guid>
          <description xml:base="https://www.gridbugs.org/7drl2025-day2/">&lt;p&gt;Today I completed the renderer, fixing several major bugs and adding the
ability to render objects which can be occluded by walls. I implemented the
oscillagraphics renderer, so now when playing the game you hear sound that
changes depending on what’s on the screen. Note the additional lines in the
rendered image which aren’t part of the world geometry or objects. These are
necessary as when rendering with oscillographics it’s impossible to “lift the
pen up”.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;7drl2025-day2&#x2F;screenshot.png&quot; alt=&quot;Screenshot of a 3d-rendered scene rendered with oscillographics&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Next up I need to implement collision detection so you can’t walk through walls.&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>7 Day Roguelike 2025: Vector 3D Renderer</title>
          <pubDate>Sat, 01 Mar 2025 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/7drl2025-day1/</link>
          <guid>https://www.gridbugs.org/7drl2025-day1/</guid>
          <description xml:base="https://www.gridbugs.org/7drl2025-day1/">&lt;p&gt;This year my plan is to make a game that can be rendered using purely oscillographics.
Your computer will play sound out of the left and right speakers such that if you plotted the two waveforms on the X&#x2F;Y axis it renders the game like an etch-a-sketch.
The game will also display the oscillographics directly just in case you don’t have a 2D oscilloscope.&lt;&#x2F;p&gt;
&lt;p&gt;Here’s my progress after a day of hacking.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;7drl2025-day1&#x2F;screenshot.png&quot; alt=&quot;Screenshot showing some top-down debugging views and a 3D render of the scene&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;This screenshot shows a 3D render of some level geometry.
The three images at the top show some debugging info. From left to right they
show the original grid-based level that was generated, a vector representation
of the same level with vertices in front of the player highlighted, and finally
the vector representation again but with all the geometry behind the player
removed and non-occluded vertices highlighted.&lt;&#x2F;p&gt;
&lt;p&gt;Rendering 3D graphics with a 2D oscilloscope means I need a 3D renderer that outputs 2D vector graphics.
This is unconventional; most 3D renderers rasterize their output and the rasterization is key to computing the visible parts of the scene.
Normally a data structure called a depth buffer keeps track of the distance
from the eye to each pixel during rendering, allowing near objects to be drawn
over the top of far objects during rasterization. Oscilloscopes draw lines -
not pixels - so I need to make sure that the only lines that get drawn are those that would be visible.&lt;&#x2F;p&gt;
&lt;p&gt;I’ll go into more detail about the algorithm I came up with for visible area
detection when I have more time, after the 7DRL.&lt;&#x2F;p&gt;
&lt;p&gt;The hardest part was removing all the geometry behind the player. This was hard
because sometimes there’s a wall that starts behind the player and ends in
front of the player. Perspective projection projects points behind the eye to
incorrect positions on the screen, so when some level geometry is both behind
and in front of the player it needs to be truncated so only the portion in
front of the player remains. Very fiddly and easy to get wrong.&lt;&#x2F;p&gt;
&lt;p&gt;Usually I use an existing engine for my 7DRL projects but this year I’m
basically starting from scratch. I’m using bevy to handle IO and draw lines,
and I’m going to use my &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;gridbugs&#x2F;caw&quot;&gt;synthesizer&lt;&#x2F;a&gt; for
generating sound. The closest thing I have to a starting point is &lt;a href=&quot;https:&#x2F;&#x2F;gridbugs.github.io&#x2F;oscillographics-cube&#x2F;&quot;&gt;this
oscillographics demo&lt;&#x2F;a&gt; I made a few weeks before the jam
to test how hard it is to generate arbitrary images with oscillographics.&lt;&#x2F;p&gt;
&lt;p&gt;Next up I need to fix a few bugs in the renderer, then switch from rendering it
directly to rendering it with oscillographics.&lt;&#x2F;p&gt;
&lt;p&gt;Here’s one of the renderer bugs:
&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;7drl2025-day1&#x2F;screenshot-broken.png&quot; alt=&quot;Similar screenshot to above however at least one wall is missing and another is rendered incorrectly with the top and bottom lines crossing over.&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Every Game I Made Since 2019 had a Lighting Bug</title>
          <pubDate>Thu, 07 Nov 2024 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/every-game-i-made-since-2019-had-a-lighting-bug/</link>
          <guid>https://www.gridbugs.org/every-game-i-made-since-2019-had-a-lighting-bug/</guid>
          <description xml:base="https://www.gridbugs.org/every-game-i-made-since-2019-had-a-lighting-bug/">&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;every-game-i-made-since-2019-had-a-lighting-bug&#x2F;banner.jpg&quot; alt=&quot;Banner from a screenshot from the game Rain Forest showing a lamp in some fog&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;I’ve used the same game engine for all the roguelikes I’ve made since 2019 and I
found out this year that they are all affected by a bug in the way lighting is
calculated. This post describes the bug, the fix, and shows the effect fixing the
bug had on all the affected games.&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;every-game-i-made-since-2019-had-a-lighting-bug&#x2F;#the-bug&quot;&gt;The bug&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;every-game-i-made-since-2019-had-a-lighting-bug&#x2F;#fixing-all-my-games&quot;&gt;Fixing all my games (with side-by-side comparisons)&lt;&#x2F;a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;every-game-i-made-since-2019-had-a-lighting-bug&#x2F;#rip&quot;&gt;Rip&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;every-game-i-made-since-2019-had-a-lighting-bug&#x2F;#slime99&quot;&gt;slime99&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;every-game-i-made-since-2019-had-a-lighting-bug&#x2F;#orbital-decay&quot;&gt;Orbital Decay&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;every-game-i-made-since-2019-had-a-lighting-bug&#x2F;#rain-forest&quot;&gt;Rain Forest&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;every-game-i-made-since-2019-had-a-lighting-bug&#x2F;#electric-organ&quot;&gt;Electric Organ&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;every-game-i-made-since-2019-had-a-lighting-bug&#x2F;#small-wolf&quot;&gt;Small Wolf&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;In late 2019 I returned to Australia after living abroad for two years and had
a few months to myself before starting my next job. I spent some of this
time consolidating my roguelike development side projects into a collection of Rust
libraries and a template that comprises the ad-hoc game engine that I’ve
been using ever since. These include libraries for path-finding, rendering grids
of text with WebGPU, a real-time particle system, a database for organizing
entities, and a lighting system which is the subject of this post.&lt;&#x2F;p&gt;
&lt;p&gt;There are two tasks the lighting system needs to do. The first is to determine
which parts of the map are illuminated by each light. This is the same problem
as detecting which parts of the map the player can see and is solved by a
library I made called &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;gridbugs&#x2F;shadowcast&quot;&gt;shadowcast&lt;&#x2F;a&gt;.
This library knows how to compute which parts of the map are visible, and which
parts of the map are lit by each light, and combine this information to
determine what the player should see.&lt;&#x2F;p&gt;
&lt;p&gt;For example this screenshot shows a player (the @-sign) standing in a room lit with white
light, and to their south there is a doorway leading into a room lit with red
light. Note the wall to the player’s right is illuminated with white light, as the
player can see the side of the wall facing the white light but not the side of
the wall facing the red light.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;every-game-i-made-since-2019-had-a-lighting-bug&#x2F;shadowcast-demo1.png&quot; alt=&quot;A square grid map showing the player in a room lit with white light next to a doorway leading south into a second room lit with red light&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Now look what happens when the player walks through the door into the room lit
with red light:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;every-game-i-made-since-2019-had-a-lighting-bug&#x2F;shadowcast-demo2.png&quot; alt=&quot;A square grid map showing the player in a room lit with red light next to a doorway leading north into a second room lit with white light&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;That same piece of wall now appears as red, because the side of the wall visible
to the player is now illuminated by red light. It’s the same wall, and as far as
game logic is concerned it’s just a row of wall tiles, but the lighting system
takes edges and corners of walls into account so tiles are rendered in colours
according to the colours of only the lights that illuminate the parts of walls
that the player can see.&lt;&#x2F;p&gt;
&lt;p&gt;This library is rock solid and I haven’t had to think about it in years; it’s so
reliable that I’ve started taking it for granted. It’s
not the source of today’s bug. I just wanted to show off a bit.&lt;&#x2F;p&gt;
&lt;p&gt;The second task performed by the lighting system is determining the intensity of
the light at each point of the map. Notice in the images above that there are
parts of the map that are well-lit, and others that appear in shadow. The lights
themselves aren’t visible in those images, but the further a point is from a
light source, the dimmer the effect of the light will be at that point. The
relationship between brightness and distance is some fairly simple arithmetic -
much much simpler than computing the lit area of the map - but I got the maths
wrong and didn’t notice for almost five years!&lt;&#x2F;p&gt;
&lt;p&gt;Well I did suspect something was wrong with it a couple of times. The brightness
of the lights seemed to drop off with distance in an unnatural-looking way. Too
slowly up to a point and then too quickly. I checked and double checked the
maths and managed to convince myself that it was correct, and then I’d just
compensate for it by messing with the brightness of the lights in whatever game
I was working on. Pretty much all the roguelike development I’ve done over the
past five years has been in a game jam setting and I was always too busy with
the jam to properly investigate. That is until this year’s 7DRL when I finally
relented and sat down with a pen and paper to conclude once and for all that
the maths was wrong and had been wrong this whole time, and when I fixed it my
game went from looking like this:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;every-game-i-made-since-2019-had-a-lighting-bug&#x2F;electric-organ-bad.png&quot; alt=&quot;Gameplay from the traditional roguelike “Electric Organ” from before this bug was fixed&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;To this:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;every-game-i-made-since-2019-had-a-lighting-bug&#x2F;electric-organ-good.png&quot; alt=&quot;The same screenshot as above, but with the lighting fixed. There’s a much bigger visual difference between the dark areas and let areas of the map.&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Obviously it’s not a fair comparison because most of the aesthetic decisions
about the game were made after fixing this bug. But getting the lighting right
was crucial to the visuals I was trying to create, with
lots of dark places and flickering fire light and it’s important that the
dynamic changes to the lighting were accurately reflected in the environment.&lt;&#x2F;p&gt;
&lt;p&gt;So now that the bug is fixed I thought it might be interesting to dig through
the history of how this code was written and to try to understand why I
implemented such a simple piece of arithmetic incorrectly, and how to fix it.
I’ll also backport the fix to all the games I’ve made since 2019 that suffer
from this bug and share some before and after pics.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-bug&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#the-bug&quot; aria-label=&quot;Anchor link for: the-bug&quot;&gt;The bug&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;The intensity of a light at a point is proportional to the inverse of the
square of the distance from the light source to that point. This is known as
the &lt;a href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;Inverse-square_law&quot;&gt;Inverse-square law&lt;&#x2F;a&gt;. The easiest way to
implement diminishing lighting is to multiply the red, green and blue components
of the light’s colour by &lt;code&gt;1 &#x2F; distance²&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;every-game-i-made-since-2019-had-a-lighting-bug&#x2F;plot1.png&quot; alt=&quot;A plot of y=1&#x2F;d², where y is Light Intensity and d = Distance&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;The first issue with this is that the function is not defined at a distance of
0. Since this lighting system is for games rendered as a grid of tiles, the
shortest possible distance between a light’s tile and any other tile is 1. Thus
my original solution was to limit the distance to the light to 1 in lighting
calculations.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;every-game-i-made-since-2019-had-a-lighting-bug&#x2F;plot2.png&quot; alt=&quot;A plot of y=1&#x2F;max(d², 1), where y is Light Intensity and d = Distance&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Now that this function is defined at all distances we can visualize it by
plotting the function on a heatmap:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;every-game-i-made-since-2019-had-a-lighting-bug&#x2F;map1.png&quot; alt=&quot;A heatmap of 1&#x2F;max(d², 1) where d is the distance from the centre&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;On the heatmap there’s a solid circle with radius 1 corresponding to the flat
part of the function below a distance of 1, and then the brightness quickly
drops off approaching 0 as the distance increases.&lt;&#x2F;p&gt;
&lt;p&gt;I wanted a way of slowing down the rate with which the light diminishes so that
it spreads out over a larger area so I added a parameter &lt;code&gt;C&lt;&#x2F;code&gt; which can be used
to tune the light diminishing rate, effectively stretching out the
curve horizontally.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;every-game-i-made-since-2019-had-a-lighting-bug&#x2F;plot3.png&quot; alt=&quot;A plot of y=1&#x2F;(C × max(d², 1)), where y is Light Intensity and d = Distance&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;And this was the bug. I didn’t account for the fact that stretching out the
curve horizontally would also stretch it &lt;em&gt;vertically&lt;&#x2F;em&gt;. Assuming the colour of
the light is white, this will effectively clip the curve at y=1, causing
saturation:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;every-game-i-made-since-2019-had-a-lighting-bug&#x2F;plot4.png&quot; alt=&quot;A plot of y=min(1, 1&#x2F;(C × max(d², 1))), where y is Light Intensity and d = Distance&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Visualizing with a heatmap, this has the effect of creating a large bright spot
in the saturated region which then quickly drops off outside that region which
matches the original symptom of the bug.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;every-game-i-made-since-2019-had-a-lighting-bug&#x2F;map2.png&quot; alt=&quot;A heatmap of min(1, 1&#x2F;(C × max(d², 1))), where d is the distance from the centre and C=0.1&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;My first thought for how to fix this would be stop flattening the curve below a
distance of 1, but instead ensure that the function is defined at a distance of
0 by &lt;em&gt;shifting&lt;&#x2F;em&gt; the entire curve to the left such that it has a value of 1 at a
distance of 0.&lt;&#x2F;p&gt;
&lt;p&gt;I switched to the formula &lt;code&gt;1 &#x2F; (Cd² + 1)&lt;&#x2F;code&gt;. Like before, &lt;code&gt;C&lt;&#x2F;code&gt; can be used to
control how quickly the light diminishes. I didn’t realize at the time that I
made this change (remember I was mid game jam!) but this function changes the
shape of the curve to be less steep around a distance of 0 as is clear in the
following plot.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;every-game-i-made-since-2019-had-a-lighting-bug&#x2F;plot5.png&quot; alt=&quot;A plot of y=1&#x2F;(Cd² + 1), where y is Light Intensity and d = Distance&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Here’s the new heatmap:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;every-game-i-made-since-2019-had-a-lighting-bug&#x2F;map3.png&quot; alt=&quot;A heatmap of 1&#x2F;(Cd² + 1), where d is the distance from the centre and C=0.1&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;I didn’t find out until revisiting the lighting maths while writing this post,
but the shape of these curves is actually physically accurate, and I had stumbled
upon the formula for the intensity of the light on the ground a horizontal
distance from a light, at a fixed height. The &lt;code&gt;C&lt;&#x2F;code&gt; parameter controls the height
of the light, with &lt;code&gt;C = 1&#x2F;height²&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;To demonstrate imagine we have a light &lt;code&gt;H&lt;&#x2F;code&gt; distance above the ground, and we’re
interested in finding the light intensity at a point &lt;code&gt;p&lt;&#x2F;code&gt; with a horizontal
distance of &lt;code&gt;d&lt;&#x2F;code&gt; from the light.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;every-game-i-made-since-2019-had-a-lighting-bug&#x2F;pythag.jpg&quot; alt=&quot;Diagram showing a light H units above the ground, and a point p with a horizontal distance d from the light. A line connecting the light to P forms the hypotenuse of a triangle, labelled with sqrt(d² + H²).&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;The line from &lt;code&gt;p&lt;&#x2F;code&gt; to the light is the hypotenuse of a right triangle, so the
distance from &lt;code&gt;p&lt;&#x2F;code&gt; to the light is &lt;code&gt;sqrt(d² + H²)&lt;&#x2F;code&gt;. Now according to the
Inverse-square law, the intensity of the light at &lt;code&gt;p&lt;&#x2F;code&gt; is proportional to &lt;code&gt;1&#x2F;sqrt(d² + H²)² = 1&#x2F;(d² + H²)&lt;&#x2F;code&gt;.
When &lt;code&gt;d=0&lt;&#x2F;code&gt;, this will have the value of &lt;code&gt;1&#x2F;H²&lt;&#x2F;code&gt; (the distance is just the height
of the light above the ground). To match the rest of the post I’d rather the
intensity be 1 at &lt;code&gt;d=0&lt;&#x2F;code&gt; so multiply the formula by &lt;code&gt;H²&lt;&#x2F;code&gt; to get &lt;code&gt;H²&#x2F;(d² + H²)&lt;&#x2F;code&gt;.
Now divide the top and bottom of this fraction by &lt;code&gt;H²&lt;&#x2F;code&gt; to get &lt;code&gt;1&#x2F;(d²&#x2F;H² + 1)&lt;&#x2F;code&gt;
which is the same as &lt;code&gt;1&#x2F;(Cd² + 1)&lt;&#x2F;code&gt; where &lt;code&gt;C = 1&#x2F;H²&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;The formula above was good enough for the game jam, but while working on this
post I wanted to take things a step further. My solution so far is physically accurate
for lights a given distance above the ground that shine uniformly in all
directions, but sometimes the effect of these lights on the ground looks too
unfocused. Occasionally I want a sharp point of light that radiates out in all
directions, diminishing at a configurable rate. &lt;code&gt;1&#x2F;d²&lt;&#x2F;code&gt; has the shape I want
but it’s not defined at 0, however &lt;code&gt;1&#x2F;(d + 1)²&lt;&#x2F;code&gt; has the same shape but is
shifted to the left such that it has the value 1 at &lt;code&gt;d=0&lt;&#x2F;code&gt;. To control the rate of
diminishing, multiply &lt;code&gt;d&lt;&#x2F;code&gt; by a constant parameter &lt;code&gt;C&lt;&#x2F;code&gt; as before:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;every-game-i-made-since-2019-had-a-lighting-bug&#x2F;plot6.png&quot; alt=&quot;A plot of y=1&#x2F;(Cd + 1)², where y is Light Intensity and d = Distance&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;The heatmap shows a much more focused point of light compared to the previous
formula:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;every-game-i-made-since-2019-had-a-lighting-bug&#x2F;map4.png&quot; alt=&quot;A heatmap of 1&#x2F;(Cd + 1)², where d is the distance from the centre and C=0.1&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;The final step is to combine the two different lighting formulae into a single
formula and add a second parameter for interpolating between them.&lt;&#x2F;p&gt;
&lt;p&gt;Recall that the unfocused lighting formula is &lt;code&gt;1&#x2F;(d²&#x2F;H² + 1)&lt;&#x2F;code&gt; and the focused
formula is &lt;code&gt;1&#x2F;(Cd + 1)²&lt;&#x2F;code&gt;. Start with the denominator of the focused formula: &lt;code&gt;(Cd + 1)²,&lt;&#x2F;code&gt;.
This is equivalent to &lt;code&gt;C²d² + 2Cd + 1&lt;&#x2F;code&gt;, so the focused formula becomes &lt;code&gt;1&#x2F;(C²d² + 2Cd + 1)&lt;&#x2F;code&gt;.
Now if we replace the constant &lt;code&gt;C&lt;&#x2F;code&gt; with the constant expression &lt;code&gt;1&#x2F;H&lt;&#x2F;code&gt;, the
second formula becomes &lt;code&gt;1&#x2F;(d²&#x2F;H² + 2d&#x2F;H + 1)&lt;&#x2F;code&gt; which is very similar to the
unfocused lighting formula. The only difference is the &lt;code&gt;2d&#x2F;H&lt;&#x2F;code&gt; term in the
denominator. We can introduce a new parameter &lt;code&gt;F&lt;&#x2F;code&gt; (for “focus”) and multiply it
by that term, and then we’ll be able to smoothly interpolate between the
unfocused and focused lighting formulae by changing &lt;code&gt;F&lt;&#x2F;code&gt; between 0 and 1.
The formula becomes &lt;code&gt;1&#x2F;(d²&#x2F;H² + 2Fd&#x2F;H + 1)&lt;&#x2F;code&gt;, which can be made more readable by
multiplying the top and bottom by &lt;code&gt;H²&lt;&#x2F;code&gt; to get &lt;code&gt;H²&#x2F;(d² + 2FHd + H²)&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;every-game-i-made-since-2019-had-a-lighting-bug&#x2F;plot7.png&quot; alt=&quot;A plot of y=H²&#x2F;(d² + 2FHd + H²), where y is Light Intensity and d = Distance&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;So now we can use a single formula to represent both focused and unfocused
lights, as well as everything in between.&lt;&#x2F;p&gt;
&lt;p&gt;Here’s a heatmap of a totally unfocused light:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;every-game-i-made-since-2019-had-a-lighting-bug&#x2F;map5.png&quot; alt=&quot;A heatmap of H²&#x2F;(d² + 2FHd + H²), where d is the distance from the centre. H=10, F=0&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Here’s a heatmap of a partially focused light:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;every-game-i-made-since-2019-had-a-lighting-bug&#x2F;map6.png&quot; alt=&quot;A heatmap of H²&#x2F;(d² + 2FHd + H²), where d is the distance from the centre. H=10, F=0.5&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;And here’s a heatmap of a focused light:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;every-game-i-made-since-2019-had-a-lighting-bug&#x2F;map7.png&quot; alt=&quot;A heatmap of H²&#x2F;(d² + 2FHd + H²), where d is the distance from the centre. H=10, F=1&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;h2 id=&quot;fixing-all-my-games&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#fixing-all-my-games&quot; aria-label=&quot;Anchor link for: fixing-all-my-games&quot;&gt;Fixing all my games&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;h3 id=&quot;rip&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#rip&quot; aria-label=&quot;Anchor link for: rip&quot;&gt;Rip&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;Rip was the project I was working on when I implemented the lighting system and
most of the rest of my roguelike game engine. It wasn’t really a game, just a
collection of demos of different engine components such as the particle system
used to implement the explosions in the videos below. The video on the right has
the lighting bug corrected. The lighting is softer and there’s more of a
gradient.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;video autoplay muted loop&gt;
  &lt;source src=&quot;rip-old-lighting.mp4&quot; type=&quot;video&#x2F;mp4&quot;&gt;
  Your browser does not support the video element.
&lt;&#x2F;video&gt;

&lt;video autoplay muted loop&gt;
  &lt;source src=&quot;rip-new-lighting.mp4&quot; type=&quot;video&#x2F;mp4&quot;&gt;
  Your browser does not support the video element.
&lt;&#x2F;video&gt;
&lt;&#x2F;p&gt;
&lt;h3 id=&quot;slime99&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#slime99&quot; aria-label=&quot;Anchor link for: slime99&quot;&gt;slime99&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;&lt;a href=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;slime99&#x2F;&quot;&gt;slime99&lt;&#x2F;a&gt; was my 7DRL entry in 2020.
The game has lots of glowing green acid pools whose brightness
dynamically changes in realtime but the effect was kind of lost until this
lighting bug was fixed.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;video autoplay muted loop&gt;
  &lt;source src=&quot;slime99-old-lighting.mp4&quot; type=&quot;video&#x2F;mp4&quot;&gt;
  Your browser does not support the video element.
&lt;&#x2F;video&gt;

&lt;video autoplay muted loop&gt;
  &lt;source src=&quot;slime99-new-lighting.mp4&quot; type=&quot;video&#x2F;mp4&quot;&gt;
  Your browser does not support the video element.
&lt;&#x2F;video&gt;
&lt;&#x2F;p&gt;
&lt;h3 id=&quot;orbital-decay&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#orbital-decay&quot; aria-label=&quot;Anchor link for: orbital-decay&quot;&gt;Orbital Decay&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;&lt;a href=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;orbital-decay&#x2F;&quot;&gt;Orbital Decay&lt;&#x2F;a&gt; was my 7DRL entry in 2021.
This game didn’t
do as much with lighting as the previous game but fixing the bug caused the
lighting gradients to become more apparent.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;video autoplay muted loop&gt;
  &lt;source src=&quot;orbital-decay-old-lighting.mp4&quot; type=&quot;video&#x2F;mp4&quot;&gt;
  Your browser does not support the video element.
&lt;&#x2F;video&gt;

&lt;video autoplay muted loop&gt;
  &lt;source src=&quot;orbital-decay-new-lighting.mp4&quot; type=&quot;video&#x2F;mp4&quot;&gt;
  Your browser does not support the video element.
&lt;&#x2F;video&gt;
&lt;&#x2F;p&gt;
&lt;h3 id=&quot;rain-forest&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#rain-forest&quot; aria-label=&quot;Anchor link for: rain-forest&quot;&gt;Rain Forest&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;&lt;a href=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;rain-forest&#x2F;&quot;&gt;Rain Forest&lt;&#x2F;a&gt; was my 7DRL entry in 2022.
This game didn’t have a lot of gameplay and was more of an exercise in using
visuals to set the mood than an actual game. Still it’s very pretty and fixing
the lighting makes it even prettier, softening up the edges on the pools of
light.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;video autoplay muted loop&gt;
  &lt;source src=&quot;rainforest1-old-lighting.mp4&quot; type=&quot;video&#x2F;mp4&quot;&gt;
  Your browser does not support the video element.
&lt;&#x2F;video&gt;

&lt;video autoplay muted loop&gt;
  &lt;source src=&quot;rainforest1-new-lighting.mp4&quot; type=&quot;video&#x2F;mp4&quot;&gt;
  Your browser does not support the video element.
&lt;&#x2F;video&gt;
&lt;&#x2F;p&gt;
&lt;p&gt;Here’s another comparison from Rain Forest. After fixing the lighting the door
of the cabin now casts a shadow, where its shadow was washed out by the
over-saturated lights in the original version.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;video autoplay muted loop&gt;
  &lt;source src=&quot;rainforest2-old-lighting.mp4&quot; type=&quot;video&#x2F;mp4&quot;&gt;
  Your browser does not support the video element.
&lt;&#x2F;video&gt;

&lt;video autoplay muted loop&gt;
  &lt;source src=&quot;rainforest2-new-lighting.mp4&quot; type=&quot;video&#x2F;mp4&quot;&gt;
  Your browser does not support the video element.
&lt;&#x2F;video&gt;
&lt;&#x2F;p&gt;
&lt;p&gt;(We skipped 2023 as that year’s game doesn’t use the lighting system.)&lt;&#x2F;p&gt;
&lt;h3 id=&quot;electric-organ&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#electric-organ&quot; aria-label=&quot;Anchor link for: electric-organ&quot;&gt;Electric Organ&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;&lt;a href=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;electric-organ&#x2F;&quot;&gt;Electric Organ&lt;&#x2F;a&gt; was my 7DRL in 2024.
I fixed the lighting bug while working on it, so it didn’t actually have this
bug. I re-introduced the bug for the purposes of comparison, but the effect is
very subtle. The corrected version is slightly darker and the lighting gradients
are more apparent. For the games made before the bug was fixed, the lights were
configured to be darker than the lights in this game to compensate for the lighting bug
which caused the lights to be oversaturated near the light source. Electric
Organ didn’t need to compensate in this way. Its lights are naturally
brighter, so the effect of re-introducing the bug is less pronounced.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;video autoplay muted loop&gt;
  &lt;source src=&quot;electric-organ-old-lighting.mp4&quot; type=&quot;video&#x2F;mp4&quot;&gt;
  Your browser does not support the video element.
&lt;&#x2F;video&gt;

&lt;video autoplay muted loop&gt;
  &lt;source src=&quot;electric-organ-new-lighting.mp4&quot; type=&quot;video&#x2F;mp4&quot;&gt;
  Your browser does not support the video element.
&lt;&#x2F;video&gt;
&lt;&#x2F;p&gt;
&lt;h3 id=&quot;small-wolf&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#small-wolf&quot; aria-label=&quot;Anchor link for: small-wolf&quot;&gt;Small Wolf&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;This bug also appears in another &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;gridbugs&#x2F;small-wolf&#x2F;&quot;&gt;small
project&lt;&#x2F;a&gt; I did a few years ago where I
recreated a simple version of the Wolfenstein 3D render in javascript.
Originally the brightness of the walls would get saturated at the max and min
brightness causing the gradients to be lost, ruining the 3D effect. I fixed that
while I was at it and it now looks much better.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;video autoplay muted loop&gt;
  &lt;source src=&quot;small-wolf-old-lighting.mp4&quot; type=&quot;video&#x2F;mp4&quot;&gt;
  Your browser does not support the video element.
&lt;&#x2F;video&gt;

&lt;video autoplay muted loop&gt;
  &lt;source src=&quot;small-wolf-new-lighting.mp4&quot; type=&quot;video&#x2F;mp4&quot;&gt;
  Your browser does not support the video element.
&lt;&#x2F;video&gt;
&lt;&#x2F;p&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#conclusion&quot; aria-label=&quot;Anchor link for: conclusion&quot;&gt;Conclusion&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;I’m not going to re-release any of the fixed games. This is partly because they
were playtested on the buggy lighting system and fixing it may cause some parts
of the games to be too dark or not look the way I originally intended. Also in
making this post I found that the graphical and web versions of the older games
(especially Rip and slime99) had bitrotted quite badly. The Rust libraries for
rendering with WebGPU and for working with WebAssembly were in a very
experimental state back in 2019, and even with lockfiles it’s
non-trivial to get some Rust libraries from over 5 years ago to play nice with the current
versions of drivers and system libraries, at least on Linux. I got them running
on MacOS though, and all the relevant git repos now have a &lt;code&gt;lighting-fix&lt;&#x2F;code&gt; branch
so curious and determined folks can try them out.&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Setting up WinGet for the First Time</title>
          <pubDate>Mon, 04 Nov 2024 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/setting-up-winget-for-the-first-time/</link>
          <guid>https://www.gridbugs.org/setting-up-winget-for-the-first-time/</guid>
          <description xml:base="https://www.gridbugs.org/setting-up-winget-for-the-first-time/">&lt;p&gt;I learnt today that Windows has a built-in package manager. I’m setting up a
machine for doing some Windows development at work, and I read that it’s now
possible to install Git by running a command from PowerShell:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;winget install --id Git.Git -e --source winget
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Well I tried that and it didn’t work:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;PS C:\windows\system32&amp;gt; winget install --id Git.Git -e --source winget
&lt;&#x2F;span&gt;&lt;span&gt;Failed in attempting to update the source: winget
&lt;&#x2F;span&gt;&lt;span&gt;Failed when searching source: winget
&lt;&#x2F;span&gt;&lt;span&gt;An unexpected error occurred while executing the command:
&lt;&#x2F;span&gt;&lt;span&gt;0x8a15000f : Data required by the source is missing
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The process of fixing it was a little strange and hard to discover so I’m
writing it down for the benefit of my future self and others.&lt;&#x2F;p&gt;
&lt;p&gt;The first thing I tried was listing the sources of the package repo:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;PS C:\windows\system32&amp;gt; winget source list
&lt;&#x2F;span&gt;&lt;span&gt;Name    Argument                                      Explicit
&lt;&#x2F;span&gt;&lt;span&gt;--------------------------------------------------------------
&lt;&#x2F;span&gt;&lt;span&gt;msstore https:&#x2F;&#x2F;storeedgefd.dsx.mp.microsoft.com&#x2F;v9.0 false
&lt;&#x2F;span&gt;&lt;span&gt;winget  https:&#x2F;&#x2F;winget.azureedge.net&#x2F;cache            false
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Some advice online suggested updating the sources which would make sense given
that this is a fresh install of Windows.&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;PS C:\Windows\system32&amp;gt; winget source update
&lt;&#x2F;span&gt;&lt;span&gt;Updating all sources...
&lt;&#x2F;span&gt;&lt;span&gt;Updating source: msstore...
&lt;&#x2F;span&gt;&lt;span&gt;Done
&lt;&#x2F;span&gt;&lt;span&gt;Updating source: winget...
&lt;&#x2F;span&gt;&lt;span&gt;Cancelled
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Something is wrong with the WinGet source as evidenced by the fact that its
update was cancelled.&lt;&#x2F;p&gt;
&lt;p&gt;After an hour or so of investigation I found this on  &lt;a href=&quot;https:&#x2F;&#x2F;learn.microsoft.com&#x2F;en-us&#x2F;windows&#x2F;package-manager&#x2F;winget&#x2F;&quot;&gt;Microsoft’s documentation about
WinGet&lt;&#x2F;a&gt;:&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;Windows Package Manager WinGet command-line tool is available on Windows 11
and modern versions of Windows 10 as a part of the App Installer.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;And there was a link to &lt;a href=&quot;https:&#x2F;&#x2F;www.microsoft.com&#x2F;store&#x2F;productId&#x2F;9NBLGGH4NNS1?ocid=pdpshare&quot;&gt;App Installer in the Microsoft
Store&lt;&#x2F;a&gt;.
Clicking the link opens the Microsoft Store program and shows the page for App
Installer.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;setting-up-winget-for-the-first-time&#x2F;screenshot.jpg&quot; alt=&quot;Microsoft Store page for “App Installer”&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;The only reason I trusted this is because it was on Microsoft’s own website.
There’s not many indications in the UI that this is an official Microsoft
application, and it’s odd that such an essential piece of software isn’t bundled
with the OS. It does seem like the machine came with an out-of-date version of
this tool, but then it’s odd that it wasn’t updated automatically while
configuring the machine the first time it’s turned on.&lt;&#x2F;p&gt;
&lt;p&gt;When I first came across the advice of installing or updating App Installer I
searched the Microsoft Store for “App Installer” and was unable to find it, so I
assumed that the advice was malicious and ignored it. But it turns out that for
some reason the search feature in Microsoft Store doesn’t include “App
Installer” in its results, so you have to get to its page by clicking on a link.&lt;&#x2F;p&gt;
&lt;p&gt;Requiring users to jump through these kinds of hoops makes it so easy to trick
people into downloading malicious software. Given that this happened on a fresh
install of Windows it stands to reason that many new computers will have the
same problem described in this post the first time someone tries to use WinGet.
The error message (&lt;code&gt;0x8a15000f : Data required by the source is missing&lt;&#x2F;code&gt;)
is not helpful, so it took an hour or so of investigation until I found the
solution. Lots of people are posting on StackOverflow and Reddit and Github
running into the same issue, and there’s no simple concise explanation among the
replies describing why the problem happens and how to solve it. When you do
eventually find out that “App Installer” needs to be installed or updated, you
can’t find that app in the Microsoft Store by searching for it - you have to
click on a link on a web page. It would be so easy to post a reply to one of
these threads directing people to download some malicious software hosted on the
Windows Store, and it would be so easy to make a more legitimate looking store
page than the one for App Installer.&lt;&#x2F;p&gt;
&lt;p&gt;After installing or updating App Installer I was able to update the WinGet sources:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;PS C:\windows\system32&amp;gt; winget source update
&lt;&#x2F;span&gt;&lt;span&gt;Updating all sources...
&lt;&#x2F;span&gt;&lt;span&gt;Updating source: msstore...
&lt;&#x2F;span&gt;&lt;span&gt;Done
&lt;&#x2F;span&gt;&lt;span&gt;Updating source: winget...
&lt;&#x2F;span&gt;&lt;span&gt;  ██████████████████████████████  100%
&lt;&#x2F;span&gt;&lt;span&gt;Done
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;And now the URL of the &lt;code&gt;winget&lt;&#x2F;code&gt; source in list of sources is different:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;PS C:\windows\system32&amp;gt; winget source list
&lt;&#x2F;span&gt;&lt;span&gt;Name    Argument                                      Explicit
&lt;&#x2F;span&gt;&lt;span&gt;--------------------------------------------------------------
&lt;&#x2F;span&gt;&lt;span&gt;msstore https:&#x2F;&#x2F;storeedgefd.dsx.mp.microsoft.com&#x2F;v9.0 false
&lt;&#x2F;span&gt;&lt;span&gt;winget  https:&#x2F;&#x2F;cdn.winget.microsoft.com&#x2F;cache        false
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Remember the previous URL was &lt;code&gt;https:&#x2F;&#x2F;winget.azureedge.net&#x2F;cache&lt;&#x2F;code&gt; which looks
like a random person registered a domain name made up of two
Microsoft-related words joined together and tried to pass it off as official.
That was the URL that was originally set as the &lt;code&gt;winget&lt;&#x2F;code&gt; source in my fresh
install of Windows so I suspect that it was run by Microsoft, but the point is
that it still looked suspicious. This whole exercise has been marred by Microsoft
doing things that look like social engineering but (hopefully!) aren’t, which
makes actual social engineering of Windows users that much easier.&lt;&#x2F;p&gt;
&lt;p&gt;But now the source for &lt;code&gt;winget&lt;&#x2F;code&gt; is under &lt;code&gt;microsoft.com&lt;&#x2F;code&gt;. I wonder if all I
needed to do was to update the source for &lt;code&gt;winget&lt;&#x2F;code&gt; to
&lt;code&gt;https:&#x2F;&#x2F;cdn.winget.microsoft.com&#x2F;cache&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;And now I can finally install Git:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;PS C:\windows\system32&amp;gt; winget install --id Git.Git -e --source winget
&lt;&#x2F;span&gt;&lt;span&gt;Found Git [Git.Git] Version 2.47.0.2
&lt;&#x2F;span&gt;&lt;span&gt;This application is licensed to you by its owner.
&lt;&#x2F;span&gt;&lt;span&gt;Microsoft is not responsible for, nor does it grant any licenses to, third-party packages.
&lt;&#x2F;span&gt;&lt;span&gt;Downloading https:&#x2F;&#x2F;github.com&#x2F;git-for-windows&#x2F;git&#x2F;releases&#x2F;download&#x2F;v2.47.0.windows.2&#x2F;Git-2.47.0.2-64-bit.exe
&lt;&#x2F;span&gt;&lt;span&gt;  ██████████████████████████████  65.5 MB &#x2F; 65.5 MB
&lt;&#x2F;span&gt;&lt;span&gt;Successfully verified installer hash
&lt;&#x2F;span&gt;&lt;span&gt;Starting package install...
&lt;&#x2F;span&gt;&lt;span&gt;Successfully installed
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
</description>
      </item>
      <item>
          <title>Restoring my Childhood Family Computer Part 3: Chime</title>
          <pubDate>Sun, 03 Nov 2024 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/restoring-my-childhood-family-computer-part-3/</link>
          <guid>https://www.gridbugs.org/restoring-my-childhood-family-computer-part-3/</guid>
          <description xml:base="https://www.gridbugs.org/restoring-my-childhood-family-computer-part-3/">&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;restoring-my-childhood-family-computer-part-3&#x2F;banner.jpg&quot; alt=&quot;Close-up of the logo on the front of my Macintosh LC575&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;In &lt;a href=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;restoring-my-childhood-family-computer-part-1&#x2F;&quot;&gt;Part 2&lt;&#x2F;a&gt; I
inspected the power supply and cleaned out a bunch of wasp nests from the
circuitry. I concluded that the power supply for the CRT seems to work and
ordered a replacement motherboard as my current one was badly damaged by a
battery leak.&lt;&#x2F;p&gt;
&lt;p&gt;This week my replacement motherboard arrived:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;restoring-my-childhood-family-computer-part-3&#x2F;new-mobo.jpg&quot; alt=&quot;A motherboard for a Macintosh LC575&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;It came with a processor already installed, as well as several sticks of RAM.
The longer one is DRAM in the two shorter ones are video RAM.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;restoring-my-childhood-family-computer-part-3&#x2F;ram.jpg&quot; alt=&quot;Three sticks of RAM. One is larger than the other two.&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;It also came with a network card with an Ethernet port. This might come in handy
for transferring data off the hard drive, as the only other way for this machine
to communicate with other machines is transferring data over floppy disk. One
goal for this project is to copy all the data from the hard drive into a format
that I can load into an emulator. I’ll put the network card aside for later.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;restoring-my-childhood-family-computer-part-3&#x2F;network-card.jpg&quot; alt=&quot;A network expansion card.&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;I installed the RAM into the new motherboard and slotted it into the machine,
plugged in the keyboard (why is the power switch on the keyboard!) and turned it
on.&lt;&#x2F;p&gt;
&lt;p&gt;And something happened. First it played the Macintosh startup chord, which is
still played by modern Macs when powering on. The fact that it even made it this
far was a relief as it means that my original motherboard was indeed dead, and
the replacement works, at least in so far as it’s capable of understanding the
“on” signal from the keyboard and playing the startup chord.&lt;&#x2F;p&gt;
&lt;p&gt;Next the machine played a series of chimes. These are known as &lt;a href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;Macintosh_startup#Chimes_of_Death&quot;&gt;Chimes of
Death&lt;&#x2F;a&gt; and
indicate a hardware problem. But at least it powers on which is a great start.&lt;&#x2F;p&gt;
&lt;p&gt;The case fan started to spin.
There was a chirping sound coming from somewhere inside the case. It sounds
similar to when a modern hard drive tries to spin up but is unable to for some reason.
I haven’t felt around to see if the drive vibrates in sync with the sound yet,
but that’s one avenue for debugging.&lt;&#x2F;p&gt;
&lt;p&gt;I also heard some strange noises from the CD drive so I
ejected it and this came out:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;restoring-my-childhood-family-computer-part-3&#x2F;kq7.jpg&quot; alt=&quot;A CD drive with a CD for Kings Quest 7&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;This was one of the first games I ever played! Getting to play it again on the
first computer I ever used is even more motivation for me to get this thing
working.&lt;&#x2F;p&gt;
&lt;p&gt;I’ve recorded &lt;a href=&quot;https:&#x2F;&#x2F;www.youtube.com&#x2F;shorts&#x2F;tkykigQ1hKk&quot;&gt;a video&lt;&#x2F;a&gt; of the
machine powering on showing the startup chord and the Chime of Death.&lt;&#x2F;p&gt;
&lt;p&gt;I’ve got a few things to try next. Firstly I want to confirm that the chirping
sound I heard was indeed the hard drive. It would be a shame if the disk has
failed but it wouldn’t surprise me too much. There also seems to be an issue
with the CD drive given the strange noises it made when turning the machine on.
I’ll try taking the CD drive out and cleaning it (maybe it’s also full of
wasps).&lt;&#x2F;p&gt;
&lt;p&gt;It’s also a little concerning that nothing has displayed on the screen yet.
Hopefully this just means that no software has run that would draw to the
screen. I’m not super familiar with CRT screens so I don’t know what to expect
them to do when the power is on but no signal is received. So far I’ve seen no
evidence that the CRT works.&lt;&#x2F;p&gt;
&lt;p&gt;Once I have the CD drive working I’ll try to get my hands on an install disk for
an operating system that’s compatible with this machine, as any install disk
must be able to display something on the screen. This will let me test out the
CRT without first needing to fix or replace the hard drive.&lt;&#x2F;p&gt;
&lt;p&gt;I also want to try replacing the hard drive with a known-good drive to see if
the weird noise continues.&lt;&#x2F;p&gt;
&lt;p&gt;Part 4 is &lt;a href=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;restoring-my-childhood-family-computer-part-4&#x2F;&quot;&gt;here&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Restoring my Childhood Family Computer Part 2: Wasps!</title>
          <pubDate>Mon, 28 Oct 2024 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/restoring-my-childhood-family-computer-part-2/</link>
          <guid>https://www.gridbugs.org/restoring-my-childhood-family-computer-part-2/</guid>
          <description xml:base="https://www.gridbugs.org/restoring-my-childhood-family-computer-part-2/">&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;restoring-my-childhood-family-computer-part-2&#x2F;banner.jpg&quot; alt=&quot;Close-up of the logo on the front of my Macintosh LC575&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;In &lt;a href=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;restoring-my-childhood-family-computer-part-1&#x2F;&quot;&gt;Part 1&lt;&#x2F;a&gt; I
inspected the motherboard and found what appears to be damage from a battery
acid leak.&lt;&#x2F;p&gt;
&lt;p&gt;Before I turn on my old Macintosh I want to take a look at its power supply in
case anything is obviously wrong with it. Step 1 was to remove the case which
was attached with T15 screws (step 0 was going and buying a T15 screwdriver).&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;restoring-my-childhood-family-computer-part-2&#x2F;unscrew.jpg&quot; alt=&quot;A Macintosh LC575 with the screen facing down and a hand holding a screwdriver over one of the screws attaching the case to the front panel&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;The plastic was quite brittle and unfortunately one of the screw housings
snapped while I attempted to remove its screw. Also the screw on the bottom of
the case had a plastic shielding around it which made it hard to reach. I
recommend buying as thin of a T15 screwdriver as you can find if you attempt
this! Mine was too thick as it’s the kind of screwdriver with a bunch of
different removable heads. I ended up improvising and jamming a wad of paper in
between the end of the screwdriver body and the T15 head to extend its reach
which let me remove the screw.&lt;&#x2F;p&gt;
&lt;p&gt;Here’s how it looks without its case. It’s a scary-looking high-voltage circuit
with giant capacitors, a glass tube which I assume houses the electron gun, and
giant coils of wire which I assume are electromagnets that aim the electron
beam.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;restoring-my-childhood-family-computer-part-2&#x2F;crt1.jpg&quot; alt=&quot;The internals of the Macintosh LC575 including the CRT&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;The next part made me nervous. CRT monitors can build up a large static charge
over the course of their life and they can hold onto this charge for decades,
even when switched off and unplugged. Before poking around near the CRT I
needed to make sure this charge was removed. I watched a lot of videos of
people doing this before attempting it myself (and you should too - don’t just
copy what you read here!). The process was to attach an alligator clip between
a rubber-handled screwdriver and the case chassis, then poking the tip of the
screwdriver under the rubber suction cap and touching it against the wire
underneath. For extra safety I had one hand in my pocket the entire time so I
wouldn’t accidentally close a high-voltage circuit between my two hands.&lt;&#x2F;p&gt;
&lt;p&gt;If the CRT had been charged there would have been a loud pop as it discharged
into the chassis, but I heard no such pop. It’s possible that the decade or so
of disuse was long enough for it to fully discharge. Also apparently some Macs
from this era had “bleeder resistors” which allowed the CRT to discharge
safely. I’m certain I did the discharge procedure correctly so I assumed from
this point on there was no part of this circuit that was dangerous to touch.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;restoring-my-childhood-family-computer-part-2&#x2F;discharge.jpg&quot; alt=&quot;A hand holding a screwdriver with an alligator clip attaching the screwdriver to the case chassis. The tip of thhe screwdriver is inserted into the suction cup covering the anode wire of the CRT.&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;(I set up a stand for my phone and recorded a video of removing the case and
attempting to discharge the CRT just in case there was a cool spark or
something.)&lt;&#x2F;p&gt;
&lt;p&gt;I couldn’t see anything obviously wrong with the power supply circuit. I was
mostly looking for bulging or burst capacitors or burn marks. What I did find
an uncomfortable amount of though were wasp nests:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;restoring-my-childhood-family-computer-part-2&#x2F;wasps.jpg&quot; alt=&quot;Potter wasp nests next to the CRT flyback transformer&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;The area I grew up had a large amount of &lt;a href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;Potter_wasp&quot;&gt;potter
wasps&lt;&#x2F;a&gt; that would lay their eggs in
these tubular mud nests. Fortunately the wasps were long gone but I still
needed to remove all the nests. They are made of mud and are quite delicate,
and if one breaks then there’ll be a bunch of dirt that I then have to clean
out of the power supply.&lt;&#x2F;p&gt;
&lt;p&gt;In the end I removed 4 clusters of nests and wasp egg shells from the machine. Gross.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;restoring-my-childhood-family-computer-part-2&#x2F;wasp-nests.jpg&quot; alt=&quot;A pile of wasp nests and egg shells&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;I then gave the power supply and CRT a good dusting with some compressed air to
remove any dust and erm… wasp mud. Here’s a photo shoot of the internals of
the case:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;restoring-my-childhood-family-computer-part-2&#x2F;crt2.jpg&quot; alt=&quot;The internals of the Macintosh LC575 including the CRT&quot; &#x2F;&gt;
&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;restoring-my-childhood-family-computer-part-2&#x2F;crt3.jpg&quot; alt=&quot;Close up of the CRT&quot; &#x2F;&gt;
&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;restoring-my-childhood-family-computer-part-2&#x2F;power-supply.jpg&quot; alt=&quot;Close up of the power supply circuit&quot; &#x2F;&gt;
&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;restoring-my-childhood-family-computer-part-2&#x2F;flyback.jpg&quot; alt=&quot;Close up of the CRT flyback transformer&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Next step was to power it on and see if anything happens. I was
relieved to hear the nowadays somewhat unfamiliar sound of a CRT powering on, and no
magic smoke. There was nothing displayed on the screen and I couldn’t feel any
static when I put my hand close to the screen. Hopefully that’s just because
it’s not receiving any signal yet (I had the motherboard removed for this first
test).&lt;&#x2F;p&gt;
&lt;p&gt;Next up I tried adding the motherboard back in just in case its damage is
purely cosmetic. I also needed to attach the keyboard because that’s where the
power button is for some reason. Alas it did not power on when I pressed it,
which honestly is not surprising at all given the state of the motherboard, but
I was still a bit disappointed.&lt;&#x2F;p&gt;
&lt;p&gt;I’m betting that the motherboard is the main source of the problem. By which I
mean I’ve spent money on a replacement I found on ebay. When it arrives I’ll
post again about trying it out.&lt;&#x2F;p&gt;
&lt;p&gt;Part 3 is &lt;a href=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;restoring-my-childhood-family-computer-part-3&#x2F;&quot;&gt;here&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Restoring my Childhood Family Computer Part 1: Rust</title>
          <pubDate>Sat, 26 Oct 2024 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/restoring-my-childhood-family-computer-part-1/</link>
          <guid>https://www.gridbugs.org/restoring-my-childhood-family-computer-part-1/</guid>
          <description xml:base="https://www.gridbugs.org/restoring-my-childhood-family-computer-part-1/">&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;restoring-my-childhood-family-computer-part-1&#x2F;banner.jpg&quot; alt=&quot;Close-up of the logo on the front of my Macintosh LC575&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;I’m going to attempt to repair my family’s old Macintosh LC575. Some of my
earliest memories are of playing games on this computer and I’m keen to check out some of
the primary school assignments I typed up on it and save files from games I
played as a kid. I visited home about 10 years ago and was disappointed to find
that it didn’t power on. I asked my mum to mail it to me and now I finally have
enough confidence with electronics to begin figuring out what’s wrong with it
and hopefully to repair it. This is the first retro computer I’ve attempted to
repair and there’s a chance that it will never work but it would be really cool
if I can fix it!&lt;&#x2F;p&gt;
&lt;p&gt;Here it is:
&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;restoring-my-childhood-family-computer-part-1&#x2F;computer.jpg&quot; alt=&quot;The Macintosh LC575&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;The first step was taking out the motherboard. There’s a plastic cover on the
back that pops off with some clips. The plastic has become so brittle that one
of the clips shattered in the process of removing it. After this the
motherboard just slides out!&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;restoring-my-childhood-family-computer-part-1&#x2F;mobo.jpg&quot; alt=&quot;The motherboard, with signs of a great deal of corrosion&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;It’s not in great shape. The peripheral plugs are very rusty, as are some of
the IC pins. What’s scarier though is the suspicious bright-green powder
clearer in this next image:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;restoring-my-childhood-family-computer-part-1&#x2F;battery-acid-leak.jpg&quot; alt=&quot;Close-up of the motherboard showing a bright-green powder&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;The first motherboard photo was from after attempting to clean up the board a
bit, but this second one shows it in its original state with a bright-green
powder on some parts of the board. I got some advice online after sharing this
photo around that this is the result of a battery leak. After cleaning it off,
some parts of the green coating on the board flake off. I don’t know yet
whether it’s impacted the connections between components.&lt;&#x2F;p&gt;
&lt;p&gt;Suggestions from asking about this online range from giving it a good wash to
accepting that it will be almost impossible to repair. Given that I mostly just
care about the contents of the hard drive (finger’s crossed it isn’t full of
rust!) I might end up replacing the motherboard if it is indeed irreparable.&lt;&#x2F;p&gt;
&lt;p&gt;That’s all for now. The next step will be taking off the shell and inspecting
the power supply.&lt;&#x2F;p&gt;
&lt;p&gt;Part 2 is &lt;a href=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;restoring-my-childhood-family-computer-part-2&#x2F;&quot;&gt;here&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Frustrating Interactions with the OCaml Ecosystem while developing a Synthesizer Library</title>
          <pubDate>Thu, 05 Sep 2024 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/frustrating-interactions-with-the-ocaml-ecosystem-while-developing-a-synthesizer-library/</link>
          <guid>https://www.gridbugs.org/frustrating-interactions-with-the-ocaml-ecosystem-while-developing-a-synthesizer-library/</guid>
          <description xml:base="https://www.gridbugs.org/frustrating-interactions-with-the-ocaml-ecosystem-while-developing-a-synthesizer-library/">&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;frustrating-interactions-with-the-ocaml-ecosystem-while-developing-a-synthesizer-library&#x2F;background.jpg&quot; alt=&quot;A landscape of a rugged mountain range&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;div style=&quot;color:gray;font-style:italic&quot;&gt;
At the time of writing I&#x27;m employed by Tarides to work on the Dune
build system, but all the opinions in this post are my own.  I wrote
the first version of this post a year ago for the Tarides blog but it
was never published.  I recently got permission to post it here
instead.
&lt;&#x2F;div&gt;
&lt;p&gt;This post is about some frustrating experiences I had while developing
my first non-trivial OCaml project - &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;gridbugs&#x2F;llama&#x2F;&quot;&gt;an audio synthesizer
library&lt;&#x2F;a&gt;.  I generally enjoy
programming in OCaml but I often find its development tools to be
counter-intuitive in their UX and surprising-in-a-bad-way in their
behaviour.  Realistic expectations are important for avoiding
disappointment and my expectations were too high when I started the
project. The goal of this post is to communicate my err…updated
expectations of OCaml development tools by listing all the times they
didn’t work the way I expected while developing my synthesizer
library.&lt;&#x2F;p&gt;
&lt;p&gt;This isn’t just a cathartic rant (though it’s also that).  I’m worried
that people will try out OCaml, encounter friction with its tools, and
bounce off to a more ergonomic ecosystem. Or worse, I’m worried that
users will attribute their negative experiences with OCaml’s tooling
as a deficiency in their own programming ability rather than a
deficiency in the tools themselves. I want to reach these people and
convey that if you are struggling with the tools, know that it’s not
you - it’s the tools. Almost every OCaml programmer I know struggles
to install packages with Opam, and struggles to configure Dune to do
anything non-trivial when building projects. OCaml tools are hard to
use. You are not alone.&lt;&#x2F;p&gt;
&lt;p&gt;My initially high expectations of OCaml tooling is possibly related to
the fact that the language I use for most of my personal programming
projects is Rust. I choose Rust for most of my hobby programming
specifically because I find it easy to manage dependencies and build
projects with Cargo. I have limited free time and I’d rather spend it
making cool stuff instead of fighting against the package manager or
build system. Which brings us to…&lt;&#x2F;p&gt;
&lt;h2 id=&quot;all-the-times-an-ocaml-dev-tool-or-library-wasted-my-time-by-doing-something-unexpected-while-i-was-developing-my-synthesizer-library&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#all-the-times-an-ocaml-dev-tool-or-library-wasted-my-time-by-doing-something-unexpected-while-i-was-developing-my-synthesizer-library&quot; aria-label=&quot;Anchor link for: all-the-times-an-ocaml-dev-tool-or-library-wasted-my-time-by-doing-something-unexpected-while-i-was-developing-my-synthesizer-library&quot;&gt;All the times an OCaml dev tool or library wasted my time by doing something unexpected while I was developing my synthesizer library&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;frustrating-interactions-with-the-ocaml-ecosystem-while-developing-a-synthesizer-library&#x2F;#linking-against-os-specific-native-libraries-with-dune-is-hard&quot;&gt;Linking against OS-specific native libraries with Dune is hard&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;frustrating-interactions-with-the-ocaml-ecosystem-while-developing-a-synthesizer-library&#x2F;#dune-silently-ignores-directories-starting-with-a-period-breaking-rust-interoperability&quot;&gt;Dune silently ignores directories starting with a period, breaking Rust interoperability&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;frustrating-interactions-with-the-ocaml-ecosystem-while-developing-a-synthesizer-library&#x2F;#the-obvious-choice-of-package-for-reading-wav-files-crashes-when-reading-wav-files&quot;&gt;The obvious choice of package for reading &lt;code&gt;.wav&lt;&#x2F;code&gt; files crashes when reading &lt;code&gt;.wav&lt;&#x2F;code&gt; files&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;frustrating-interactions-with-the-ocaml-ecosystem-while-developing-a-synthesizer-library&#x2F;#transferring-an-array-of-floats-from-rust-to-ocaml-produced-a-broken-array-this-is-now-fixed&quot;&gt;Transferring an array of floats from Rust to OCaml produced a broken array (this is now fixed!)&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;frustrating-interactions-with-the-ocaml-ecosystem-while-developing-a-synthesizer-library&#x2F;#adding-inline-tests-to-a-library-requires-adding-over-20-runtime-dependencies&quot;&gt;Adding inline tests to a library requires adding over 20 (runtime) dependencies&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;frustrating-interactions-with-the-ocaml-ecosystem-while-developing-a-synthesizer-library&#x2F;#dune-can-generate-opam-files-but-requires-a-workaround-for-adding-the-available-field&quot;&gt;Dune can generate &lt;code&gt;.opam&lt;&#x2F;code&gt; files but requires a workaround for adding the &lt;code&gt;available&lt;&#x2F;code&gt; field&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;frustrating-interactions-with-the-ocaml-ecosystem-while-developing-a-synthesizer-library&#x2F;#if-some-but-not-all-of-the-interdependent-packages-in-a-project-are-released-opam-can-t-solve-the-project-s-dependencies&quot;&gt;If some (but not all) of the interdependent packages in a project are released, Opam can’t solve the project’s dependencies&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h3 id=&quot;linking-against-os-specific-native-libraries-with-dune-is-hard&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#linking-against-os-specific-native-libraries-with-dune-is-hard&quot; aria-label=&quot;Anchor link for: linking-against-os-specific-native-libraries-with-dune-is-hard&quot;&gt;Linking against OS-specific native libraries with Dune is hard&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;The first thing I needed to do was make a program that plays a simple
sound.  The most reliable cross-platform library I’m aware of for
interfacing with sound drivers is
&lt;a href=&quot;https:&#x2F;&#x2F;crates.io&#x2F;crates&#x2F;cpal&quot;&gt;&lt;code&gt;cpal&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;.  One problem: It’s a Rust
library. (I’ve since learned of
&lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;savonet&#x2F;ocaml-ao&quot;&gt;&lt;code&gt;ocaml-ao&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; which provides
bindings for the cross-platform audio library &lt;code&gt;libao&lt;&#x2F;code&gt;.)&lt;&#x2F;p&gt;
&lt;p&gt;Calling into Rust from OCaml was easier than I expected
thanks to the &lt;a href=&quot;https:&#x2F;&#x2F;crates.io&#x2F;crates&#x2F;ocaml&quot;&gt;&lt;code&gt;ocaml-rs&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;
library. I used &lt;code&gt;ocaml-rs&lt;&#x2F;code&gt; and &lt;code&gt;cpal&lt;&#x2F;code&gt; to make a small Rust library
named &lt;code&gt;low_level&lt;&#x2F;code&gt; which accepts a stream of &lt;code&gt;float&lt;&#x2F;code&gt;s representing
audio data, and plays them on the computer’s speakers. I compiled this
library to an archive file &lt;code&gt;liblow_level.a&lt;&#x2F;code&gt;, and wrote a little OCaml
library &lt;code&gt;llama_low_level&lt;&#x2F;code&gt; (my synthesizer library is named &lt;code&gt;llama&lt;&#x2F;code&gt;) to
wrap it with a higher-level interface.&lt;&#x2F;p&gt;
&lt;p&gt;I’m using Dune to build this project. Dune has a mechanism for linking
against native libraries like &lt;code&gt;liblow_level.a&lt;&#x2F;code&gt;, and the
&lt;a href=&quot;https:&#x2F;&#x2F;zshipko.github.io&#x2F;ocaml-rs&#x2F;&quot;&gt;&lt;code&gt;ocaml-rs&lt;&#x2F;code&gt; documentation&lt;&#x2F;a&gt; gives
some advice on how to write your &lt;code&gt;dune&lt;&#x2F;code&gt; file (the per-directory build
config files used by Dune). I started with this:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;dune&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-dune &quot;&gt;&lt;code class=&quot;language-dune&quot; data-lang=&quot;dune&quot;&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;library
&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;name&lt;&#x2F;span&gt;&lt;span&gt; llama_low_level)
&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;foreign_archives&lt;&#x2F;span&gt;&lt;span&gt; low_level)
&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;c_library_flags
&lt;&#x2F;span&gt;&lt;span&gt;  (-lpthread -lc -lm)))
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Building this library gave the error:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;$ dune build
&lt;&#x2F;span&gt;&lt;span&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;Error: No rule found for dlllow_level.so
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Dune is looking for the shared library file &lt;code&gt;dlllow_level.so&lt;&#x2F;code&gt; for my
&lt;code&gt;low_level&lt;&#x2F;code&gt; library, but I only compiled &lt;code&gt;low_level&lt;&#x2F;code&gt; to a static
archive &lt;code&gt;liblow_level.a&lt;&#x2F;code&gt;. There’s no reason the linker needs to be
provided with both of these files, so I took a look through
&lt;a href=&quot;https:&#x2F;&#x2F;dune.readthedocs.io&#x2F;en&#x2F;stable&#x2F;dune-files.html#library&quot;&gt;dune’s (library …) stanza documentation&lt;&#x2F;a&gt;
to see if there’s a way to only link against the static library and
discovered the &lt;code&gt;no_dynlink&lt;&#x2F;code&gt; field:&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;(no_dynlink)&lt;&#x2F;code&gt; disables dynamic linking of the library. This is for advanced use only. By default, you shouldn’t set this option.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;A little intimidating but let’s give it a shot:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;dune&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-dune &quot;&gt;&lt;code class=&quot;language-dune&quot; data-lang=&quot;dune&quot;&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;library
&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;name&lt;&#x2F;span&gt;&lt;span&gt; llama_low_level)
&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;no_dynlink&lt;&#x2F;span&gt;&lt;span&gt;)                 &lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;; &amp;lt;-- the new field
&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;foreign_archives&lt;&#x2F;span&gt;&lt;span&gt; low_level)
&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;c_library_flags
&lt;&#x2F;span&gt;&lt;span&gt;  (-lpthread -lc -lm)))
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Running &lt;code&gt;dune build&lt;&#x2F;code&gt; on the &lt;code&gt;llama_low_level&lt;&#x2F;code&gt; library now works as expected.&lt;&#x2F;p&gt;
&lt;p&gt;Next step is to actually make some noise. I made a little program
simply named &lt;code&gt;experiment&lt;&#x2F;code&gt; which uses &lt;code&gt;llama_low_level&lt;&#x2F;code&gt; to play a sine
wave. Here’s its &lt;code&gt;dune&lt;&#x2F;code&gt; file:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;dune&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-dune &quot;&gt;&lt;code class=&quot;language-dune&quot; data-lang=&quot;dune&quot;&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;executable
&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;public_name&lt;&#x2F;span&gt;&lt;span&gt; experiment)
&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;libraries&lt;&#x2F;span&gt;&lt;span&gt; llama_low_level))
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Trying to build this program filled my screen with errors. The first error was:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;$ dune build
&lt;&#x2F;span&gt;&lt;span&gt;File &amp;quot;bin&#x2F;dune&amp;quot;, line 2, characters 14-24:
&lt;&#x2F;span&gt;&lt;span&gt;2 |  (public_name experiment)
&lt;&#x2F;span&gt;&lt;span&gt;                  ^^^^^^^^^^
&lt;&#x2F;span&gt;&lt;span&gt;Undefined symbols for architecture arm64:
&lt;&#x2F;span&gt;&lt;span&gt;  &amp;quot;_AudioComponentFindNext&amp;quot;, referenced from:
&lt;&#x2F;span&gt;&lt;span&gt;      cpal::host::coreaudio::macos::audio_unit_from_device::h062e0db473d1abd3 in liblow_level.a...
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;I searched the web for &lt;code&gt;AudioComponentFindNext&lt;&#x2F;code&gt; which led me to some
Apple developer docs for the AudioToolbox framework. So the foreign
archive must depend on some frameworks on MacOS for doing audio
stuff and I need to tell the linker about it. Eventually I found the
appropriate linker flags to copy&#x2F;paste from stack overflow:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;-framework CoreServices -framework CoreAudio -framework AudioUnit -framework AudioToolbox
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Now I have to find a way to pass these flags to the linker. If this
was a C program I could pass them via the C compiler. Something like:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;clang foo.c -Wl,-framework,CoreServices,-framework,CoreAudio,-framework,AudioUnit,-framework,AudioToolbox
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Dune provides a mechanism for passing additional linker flags when compiling a library:&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;(library_flags (&amp;lt;flags&amp;gt;))&lt;&#x2F;code&gt; is a list of flags passed to ocamlc and ocamlopt when building the library archive files.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;And similar to the C compiler example above, the OCaml compiler also
has a way of passing custom flags along to the linker. From &lt;code&gt;man ocamlc&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;-cclib -llibname&lt;&#x2F;p&gt;
&lt;p&gt;Pass the -llibname option to the C linker when linking in “custom
runtime” mode (see the -custom option). This causes the given C
library to be linked with the program.&lt;&#x2F;p&gt;
&lt;p&gt;-ccopt option&lt;&#x2F;p&gt;
&lt;p&gt;Pass the given option to the C compiler and linker, when linking
in “custom runtime” mode (see the -custom option). For instance,
-ccopt -Ldir causes the C linker to search for C libraries in
directory dir.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;It’s not immediately clear which of those two options I need. After some trial and error &lt;code&gt;-cclib&lt;&#x2F;code&gt; turns out to be the winner.&lt;&#x2F;p&gt;
&lt;p&gt;I added a &lt;code&gt;library_flags&lt;&#x2F;code&gt; field to the &lt;code&gt;dune&lt;&#x2F;code&gt; file for &lt;code&gt;llama_low_level&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;dune&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-dune &quot;&gt;&lt;code class=&quot;language-dune&quot; data-lang=&quot;dune&quot;&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;library
&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;name&lt;&#x2F;span&gt;&lt;span&gt; llama_low_level)
&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;no_dynlink&lt;&#x2F;span&gt;&lt;span&gt;)
&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;foreign_archives&lt;&#x2F;span&gt;&lt;span&gt; low_level)
&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;c_library_flags
&lt;&#x2F;span&gt;&lt;span&gt;  (-lpthread -lc -lm))
&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;library_flags
&lt;&#x2F;span&gt;&lt;span&gt;  (-cclib &lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;-framework CoreServices -framework CoreAudio -framework AudioUnit -framework AudioToolbox&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)))
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;That’s sufficient to get it building on MacOS and the sine wave now
playing through my speakers was music to my ears.&lt;&#x2F;p&gt;
&lt;p&gt;But what about on Linux?&lt;&#x2F;p&gt;
&lt;p&gt;On Linux &lt;code&gt;cpal&lt;&#x2F;code&gt; uses the library &lt;code&gt;libasound&lt;&#x2F;code&gt; to interface with the
sound driver. You might get away with just passing &lt;code&gt;-lasound&lt;&#x2F;code&gt;
to the linker but in general you should probe the current machine for
linker arguments by running &lt;code&gt;pkg-config --libs alsa&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;For example I run NixOS (by the way) where the correct linker arguments are:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;$ pkg-config --libs alsa
&lt;&#x2F;span&gt;&lt;span&gt;-L&#x2F;nix&#x2F;store&#x2F;g3a56c2y6arvxyr4kxvlg409gzfwyfp0-alsa-lib-1.2.11&#x2F;lib -lasound
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;As an experiment I modified the &lt;code&gt;dune&lt;&#x2F;code&gt; file to have the output of
&lt;code&gt;pkg-config&lt;&#x2F;code&gt; hard-coded to see if that was enough for it to work on
Linux:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;dune&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-dune &quot;&gt;&lt;code class=&quot;language-dune&quot; data-lang=&quot;dune&quot;&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;library
&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;name&lt;&#x2F;span&gt;&lt;span&gt; llama_low_level)
&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;no_dynlink&lt;&#x2F;span&gt;&lt;span&gt;)
&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;foreign_archives&lt;&#x2F;span&gt;&lt;span&gt; low_level)
&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;c_library_flags
&lt;&#x2F;span&gt;&lt;span&gt;  (-lpthread -lc -lm))
&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;library_flags
&lt;&#x2F;span&gt;&lt;span&gt;  (-cclib &lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;-L&#x2F;nix&#x2F;store&#x2F;g3a56c2y6arvxyr4kxvlg409gzfwyfp0-alsa-lib-1.2.11&#x2F;lib -lasound&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)))
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This works. Interestingly passing &lt;code&gt;-lasound&lt;&#x2F;code&gt; causes the library to be
linked against the shared library file &lt;code&gt;libasound.so&lt;&#x2F;code&gt; despite the
&lt;code&gt;(no_dynlink)&lt;&#x2F;code&gt; setting.&lt;&#x2F;p&gt;
&lt;p&gt;Obviously we can’t leave the linker arguments hard-coded like
that. Instead we need to get Dune to invoke &lt;code&gt;pkg-config&lt;&#x2F;code&gt; at build time
so the correct arguments for the current machine are passed to the
linker. First we’ll make it so the linker arguments are loaded from a
file, then we’ll have Dune generate that file at build time by running
&lt;code&gt;pkg-config&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Dune allows the contents of
&lt;a href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;S-expression&quot;&gt;S-expression (sexp)&lt;&#x2F;a&gt; files to be
included in most fields. I made a file &lt;code&gt;library_flags.sexp&lt;&#x2F;code&gt; with the
contents:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;(&amp;quot;-cclib&amp;quot; &amp;quot;-L&#x2F;nix&#x2F;store&#x2F;g3a56c2y6arvxyr4kxvlg409gzfwyfp0-alsa-lib-1.2.11&#x2F;lib -lasound&amp;quot;)
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The parentheses are necessary to make it a valid sexp file. The quotes
around &lt;code&gt;-cclib&lt;&#x2F;code&gt; are necessary as otherwise Dune tries to interpret
&lt;code&gt;-cclib&lt;&#x2F;code&gt; as a keyword instead of treating the entire sexp as a list
(similar to the &lt;code&gt;quote&lt;&#x2F;code&gt; keyword in some lisp dialects).&lt;&#x2F;p&gt;
&lt;p&gt;Now this file can be included in the &lt;code&gt;dune&lt;&#x2F;code&gt; file:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;dune&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-dune &quot;&gt;&lt;code class=&quot;language-dune&quot; data-lang=&quot;dune&quot;&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;library
&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;name&lt;&#x2F;span&gt;&lt;span&gt; llama_low_level)
&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;no_dynlink&lt;&#x2F;span&gt;&lt;span&gt;)
&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;foreign_archives&lt;&#x2F;span&gt;&lt;span&gt; low_level)
&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;c_library_flags
&lt;&#x2F;span&gt;&lt;span&gt;  (-lpthread -lc -lm))
&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;library_flags
&lt;&#x2F;span&gt;&lt;span&gt;  (&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;:include&lt;&#x2F;span&gt;&lt;span&gt; library_flags.sexp)))  &lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;; &amp;lt;- here!
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Next we’ll need to generate &lt;code&gt;library_flags.sexp&lt;&#x2F;code&gt; by running
&lt;code&gt;pkg-config&lt;&#x2F;code&gt; at build time. This can be done using Dune’s custom rule
mechanism. See
&lt;a href=&quot;https:&#x2F;&#x2F;dune.readthedocs.io&#x2F;en&#x2F;stable&#x2F;reference&#x2F;actions&#x2F;index.html&quot;&gt;this page&lt;&#x2F;a&gt;
for info about the different ways files can be generated by custom
rules. The &lt;code&gt;dune&lt;&#x2F;code&gt; file is now:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;dune&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-dune &quot;&gt;&lt;code class=&quot;language-dune&quot; data-lang=&quot;dune&quot;&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;rule
&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;action
&lt;&#x2F;span&gt;&lt;span&gt;  (&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;with-stdout-to
&lt;&#x2F;span&gt;&lt;span&gt;   library_flags.sexp
&lt;&#x2F;span&gt;&lt;span&gt;   (&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;progn
&lt;&#x2F;span&gt;&lt;span&gt;    (&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;echo &lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;\&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;-cclib&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;\&amp;quot; \&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)
&lt;&#x2F;span&gt;&lt;span&gt;    (&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;bash &lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;pkg-config --libs alsa&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)
&lt;&#x2F;span&gt;&lt;span&gt;    (&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;echo &lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;\&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;)&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)))))
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;library
&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;name&lt;&#x2F;span&gt;&lt;span&gt; llama_low_level)
&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;no_dynlink&lt;&#x2F;span&gt;&lt;span&gt;)
&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;foreign_archives&lt;&#x2F;span&gt;&lt;span&gt; low_level)
&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;c_library_flags
&lt;&#x2F;span&gt;&lt;span&gt;  (-lpthread -lc -lm))
&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;library_flags
&lt;&#x2F;span&gt;&lt;span&gt;  (&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;:include&lt;&#x2F;span&gt;&lt;span&gt; library_flags.sexp)))
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This feels a little cumbersome to me. I need to generate a sexp file
in one place only to include it in another place, and Dune doesn’t
help at all with emitting sexp syntax, requiring me to explicitly
escape quotes and remember to include the parentheses.&lt;&#x2F;p&gt;
&lt;p&gt;Also this solution is specific to Linux. To add back support for MacOS
we need to conditionally enable the rule and add a second rule for
MacOS that generates a sexp file with the original linker arguments:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;dune&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-dune &quot;&gt;&lt;code class=&quot;language-dune&quot; data-lang=&quot;dune&quot;&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;rule
&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;enabled_if
&lt;&#x2F;span&gt;&lt;span&gt;  (&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;%{system}&lt;&#x2F;span&gt;&lt;span&gt; linux))
&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;action
&lt;&#x2F;span&gt;&lt;span&gt;  (&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;with-stdout-to
&lt;&#x2F;span&gt;&lt;span&gt;   library_flags.sexp
&lt;&#x2F;span&gt;&lt;span&gt;   (&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;progn
&lt;&#x2F;span&gt;&lt;span&gt;    (&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;echo &lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;\&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;-cclib&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;\&amp;quot; \&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)
&lt;&#x2F;span&gt;&lt;span&gt;    (&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;bash &lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;pkg-config --libs alsa&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)
&lt;&#x2F;span&gt;&lt;span&gt;    (&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;echo &lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;\&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;)&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)))))
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;rule
&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;enabled_if
&lt;&#x2F;span&gt;&lt;span&gt;  (&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;%{system}&lt;&#x2F;span&gt;&lt;span&gt; macosx))
&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;action
&lt;&#x2F;span&gt;&lt;span&gt;  (&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;write-file
&lt;&#x2F;span&gt;&lt;span&gt;   library_flags.sexp
&lt;&#x2F;span&gt;&lt;span&gt;   &lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;\&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;-ccopt&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;\&amp;quot; \&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;-framework CoreServices -framework CoreAudio -framework AudioUnit -framework AudioToolbox&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;\&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;)&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)))
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;rule
&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;enabled_if
&lt;&#x2F;span&gt;&lt;span&gt;  (&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;and
&lt;&#x2F;span&gt;&lt;span&gt;   (&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;lt;&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;%{system}&lt;&#x2F;span&gt;&lt;span&gt; linux)
&lt;&#x2F;span&gt;&lt;span&gt;   (&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;lt;&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;%{system}&lt;&#x2F;span&gt;&lt;span&gt; macosx)))
&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;action
&lt;&#x2F;span&gt;&lt;span&gt;  (&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;write-file&lt;&#x2F;span&gt;&lt;span&gt; library_flags.sexp &lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;()&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)))
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;library
&lt;&#x2F;span&gt;&lt;span&gt; ...
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Note that the third rule is necessary so that the
&lt;code&gt;library_flags.sexp&lt;&#x2F;code&gt; file is still generated on machines that are
running neither MacOS nor Linux. Also note that the comparisons &lt;code&gt;(= %{system} linux)&lt;&#x2F;code&gt; and &lt;code&gt;(= %{system} macosx)&lt;&#x2F;code&gt; are string comparisons,
so if you accidentally typed &lt;code&gt;macos&lt;&#x2F;code&gt; instead of &lt;code&gt;macosx&lt;&#x2F;code&gt; then the
condition would be false on MacOS machines.&lt;&#x2F;p&gt;
&lt;p&gt;In this project I found that it was getting out of hand to manage the
conditional rules and to generate sexp files using Dune’s built-in
configuration language. Fortunately there is an external library
&lt;a href=&quot;https:&#x2F;&#x2F;opam.ocaml.org&#x2F;packages&#x2F;dune-configurator&quot;&gt;&lt;code&gt;dune-configurator&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;
to help you write OCaml programs that query the current machine and
generate sexp files for inclusion in &lt;code&gt;dune&lt;&#x2F;code&gt; files.&lt;&#x2F;p&gt;
&lt;p&gt;In a separate directory, I made a little executable called &lt;code&gt;discover&lt;&#x2F;code&gt; with this &lt;code&gt;dune&lt;&#x2F;code&gt; file:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;dune&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-dune &quot;&gt;&lt;code class=&quot;language-dune&quot; data-lang=&quot;dune&quot;&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;executable
&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;name&lt;&#x2F;span&gt;&lt;span&gt; discover)
&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;libraries&lt;&#x2F;span&gt;&lt;span&gt; dune-configurator))
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Then I rewrote the logic from the custom Dune rules into OCaml:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;ocaml&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-ocaml &quot;&gt;&lt;code class=&quot;language-ocaml&quot; data-lang=&quot;ocaml&quot;&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;module &lt;&#x2F;span&gt;&lt;span&gt;C &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span&gt;Configurator.V1
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;let &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;macos_library_flags &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=
&lt;&#x2F;span&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;let &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;frameworks &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=
&lt;&#x2F;span&gt;&lt;span&gt;    [ &lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;CoreServices&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;CoreAudio&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;CoreMidi&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;AudioUnit&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;AudioToolbox&amp;quot; &lt;&#x2F;span&gt;&lt;span&gt;]
&lt;&#x2F;span&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;in
&lt;&#x2F;span&gt;&lt;span&gt;  List.map (Printf.sprintf &lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;-framework %s&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;) frameworks
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;let &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;() &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=
&lt;&#x2F;span&gt;&lt;span&gt;  C.main &lt;&#x2F;span&gt;&lt;span style=&quot;color:#63a35c;&quot;&gt;~name:&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;llama_low_level&amp;quot; &lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;fun &lt;&#x2F;span&gt;&lt;span&gt;c -&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;      &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;let &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;linker_args &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;match &lt;&#x2F;span&gt;&lt;span&gt;C.ocaml_config_var_exn c &lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;system&amp;quot; &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;with
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;| &lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&amp;quot;macosx&amp;quot; &lt;&#x2F;span&gt;&lt;span&gt;-&amp;gt; macos_library_flags
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;| &lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&amp;quot;linux&amp;quot; &lt;&#x2F;span&gt;&lt;span&gt;-&amp;gt; (
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;let &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;default &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span&gt;[ &lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;-lasound&amp;quot; &lt;&#x2F;span&gt;&lt;span&gt;] &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;in
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;match &lt;&#x2F;span&gt;&lt;span&gt;C.Pkg_config.get c &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;with
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;| &lt;&#x2F;span&gt;&lt;span&gt;None -&amp;gt; default
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;| &lt;&#x2F;span&gt;&lt;span&gt;Some pc -&amp;gt; (
&lt;&#x2F;span&gt;&lt;span&gt;                &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;match &lt;&#x2F;span&gt;&lt;span&gt;C.Pkg_config.query pc &lt;&#x2F;span&gt;&lt;span style=&quot;color:#63a35c;&quot;&gt;~package:&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;alsa&amp;quot; &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;with
&lt;&#x2F;span&gt;&lt;span&gt;                &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;| &lt;&#x2F;span&gt;&lt;span&gt;None -&amp;gt; default
&lt;&#x2F;span&gt;&lt;span&gt;                &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;| &lt;&#x2F;span&gt;&lt;span&gt;Some conf -&amp;gt; conf.libs))
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;| &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;_ &lt;&#x2F;span&gt;&lt;span&gt;-&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;[]
&lt;&#x2F;span&gt;&lt;span&gt;      &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;in
&lt;&#x2F;span&gt;&lt;span&gt;      &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;let &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;cclib_arg &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span&gt;String.concat &lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot; &amp;quot;&lt;&#x2F;span&gt;&lt;span&gt; linker_args &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;in
&lt;&#x2F;span&gt;&lt;span&gt;      C.Flags.write_sexp &lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;library_flags.sexp&amp;quot; &lt;&#x2F;span&gt;&lt;span&gt;[ &lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;-cclib&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;; cclib_arg ]
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Finally I updated the &lt;code&gt;dune&lt;&#x2F;code&gt; file for &lt;code&gt;llama_low_level&lt;&#x2F;code&gt; to run &lt;code&gt;discover&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;dune&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-dune &quot;&gt;&lt;code class=&quot;language-dune&quot; data-lang=&quot;dune&quot;&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;rule
&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;target&lt;&#x2F;span&gt;&lt;span&gt; library_flags.sexp)
&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;action
&lt;&#x2F;span&gt;&lt;span&gt;  (&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;run&lt;&#x2F;span&gt;&lt;span&gt; .&#x2F;config&#x2F;discover.exe)))
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;library
&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;name&lt;&#x2F;span&gt;&lt;span&gt; llama_low_level)
&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;no_dynlink&lt;&#x2F;span&gt;&lt;span&gt;)
&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;foreign_archives&lt;&#x2F;span&gt;&lt;span&gt; low_level)
&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;c_library_flags
&lt;&#x2F;span&gt;&lt;span&gt;  (-lpthread -lc -lm))
&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;library_flags
&lt;&#x2F;span&gt;&lt;span&gt;  (&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;:include&lt;&#x2F;span&gt;&lt;span&gt; library_flags.sexp)))
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Now when Dune needs to generate the &lt;code&gt;library_flags.sexp&lt;&#x2F;code&gt; file it will
first build the &lt;code&gt;discover&lt;&#x2F;code&gt; executable and then run it to generate the
file, before including the contents of that file to set the extra
flags passed to the OCaml compiler to configure the linker.
While this does work, it feels like a &lt;a href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;Rube_Goldberg_machine&quot;&gt;Rube Goldberg Machine&lt;&#x2F;a&gt;, and I was
surprised to find that such a complex solution was needed to pass
different linker flags while building on different operating systems.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;dune-silently-ignores-directories-starting-with-a-period-breaking-rust-interoperability&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#dune-silently-ignores-directories-starting-with-a-period-breaking-rust-interoperability&quot; aria-label=&quot;Anchor link for: dune-silently-ignores-directories-starting-with-a-period-breaking-rust-interoperability&quot;&gt;Dune silently ignores directories starting with a period, breaking Rust interoperability&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;In the previous section I mentioned compiling a Rust library &lt;code&gt;liblow_level.a&lt;&#x2F;code&gt; and calling into it from OCaml.
Up until now I was running the commands to build it myself, but I’d rather have Dune do this for me.
I added this rule to the &lt;code&gt;dune&lt;&#x2F;code&gt; file for &lt;code&gt;llama_low_level&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;dune&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-dune &quot;&gt;&lt;code class=&quot;language-dune&quot; data-lang=&quot;dune&quot;&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;rule
&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;target&lt;&#x2F;span&gt;&lt;span&gt; liblow_level.a)
&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;deps
&lt;&#x2F;span&gt;&lt;span&gt;  (&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;source_tree&lt;&#x2F;span&gt;&lt;span&gt; low-level-rust))
&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;action
&lt;&#x2F;span&gt;&lt;span&gt;  (&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;progn
&lt;&#x2F;span&gt;&lt;span&gt;   (&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;chdir
&lt;&#x2F;span&gt;&lt;span&gt;    low-level-rust
&lt;&#x2F;span&gt;&lt;span&gt;    (&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;run&lt;&#x2F;span&gt;&lt;span&gt; cargo build --release))
&lt;&#x2F;span&gt;&lt;span&gt;   (&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;run&lt;&#x2F;span&gt;&lt;span&gt; mv low-level-rust&#x2F;target&#x2F;release&#x2F;%{target} %{target}))))
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Now if any of the Rust code inside the &lt;code&gt;low-level-rust&lt;&#x2F;code&gt; directory
changes, Dune will invoke Cargo to rebuild &lt;code&gt;liblow_level.a&lt;&#x2F;code&gt; before
relinking the OCaml library against the new version. One problem with
the code above is that running &lt;code&gt;cargo build --release&lt;&#x2F;code&gt; will download
any Rust dependencies before building the Rust library. This is a
problem because I intend to release my library on Opam, and when Opam
installs a package it doesn’t allow build commands to access the
network. The solution is to vendor any rust dependencies inside the
project so they are already available when &lt;code&gt;cargo build --release&lt;&#x2F;code&gt;
runs.&lt;&#x2F;p&gt;
&lt;p&gt;To vendor Rust dependencies just run &lt;code&gt;cargo vendor&lt;&#x2F;code&gt; in the Rust
project and create the file &lt;code&gt;.cargo&#x2F;config.toml&lt;&#x2F;code&gt; at the top level of
the Rust project with the contents:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;toml&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-toml &quot;&gt;&lt;code class=&quot;language-toml&quot; data-lang=&quot;toml&quot;&gt;&lt;span&gt;[source.crates-io]
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#63a35c;&quot;&gt;replace-with &lt;&#x2F;span&gt;&lt;span&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;vendored-sources&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;[source.vendored-sources]
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#63a35c;&quot;&gt;directory &lt;&#x2F;span&gt;&lt;span&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;vendor&amp;quot;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Finally, to make sure that &lt;code&gt;cargo build&lt;&#x2F;code&gt; doesn’t try to access the
network, I updated the &lt;code&gt;dune&lt;&#x2F;code&gt; file to call &lt;code&gt;cargo build --release --offline&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Testing this out:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;$ dune build
&lt;&#x2F;span&gt;&lt;span&gt;File &amp;quot;src&#x2F;low-level&#x2F;dune&amp;quot;, line 1, characters 0-224:
&lt;&#x2F;span&gt;&lt;span&gt; 1 | (rule
&lt;&#x2F;span&gt;&lt;span&gt; 2 |  (target liblow_level.a)
&lt;&#x2F;span&gt;&lt;span&gt; 3 |  (deps
&lt;&#x2F;span&gt;&lt;span&gt; 4 |   (source_tree low-level-rust))
&lt;&#x2F;span&gt;&lt;span&gt; 5 |  (action
&lt;&#x2F;span&gt;&lt;span&gt; 6 |   (progn
&lt;&#x2F;span&gt;&lt;span&gt; 7 |    (chdir
&lt;&#x2F;span&gt;&lt;span&gt; 8 |     low-level-rust
&lt;&#x2F;span&gt;&lt;span&gt; 9 |     (run cargo build --release --offline))
&lt;&#x2F;span&gt;&lt;span&gt;10 |    (run mv low-level-rust&#x2F;target&#x2F;release&#x2F;%{target} %{target}))))
&lt;&#x2F;span&gt;&lt;span&gt;error: no matching package named `cpal` found
&lt;&#x2F;span&gt;&lt;span&gt;location searched: registry `crates-io`
&lt;&#x2F;span&gt;&lt;span&gt;required by package `low_level v0.1.0
&lt;&#x2F;span&gt;&lt;span&gt;(&#x2F;Users&#x2F;s&#x2F;src&#x2F;llama&#x2F;_build&#x2F;default&#x2F;src&#x2F;low-level&#x2F;low-level-rust)`
&lt;&#x2F;span&gt;&lt;span&gt;As a reminder, you&amp;#39;re using offline mode (--offline) which can sometimes
&lt;&#x2F;span&gt;&lt;span&gt;cause surprising resolution failures, if this error is too confusing you
&lt;&#x2F;span&gt;&lt;span&gt;may wish to retry without the offline flag.
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Cargo claims that the &lt;code&gt;cpal&lt;&#x2F;code&gt; package can’t be found. This is
surprising because the vendored copy of &lt;code&gt;cpal&lt;&#x2F;code&gt; has definitely been
copied into the &lt;code&gt;_build&lt;&#x2F;code&gt; directory:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;$ ls _build&#x2F;default&#x2F;src&#x2F;low-level&#x2F;low-level-rust&#x2F;vendor&#x2F;cpal&#x2F;
&lt;&#x2F;span&gt;&lt;span&gt;examples  CHANGELOG.md  Cargo.toml  Dockerfile  README.md
&lt;&#x2F;span&gt;&lt;span&gt;src       Cargo.lock    Cross.toml  LICENSE     build.rs
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;After poking around a bit I noticed that the &lt;code&gt;.cargo&#x2F;config.toml&lt;&#x2F;code&gt;
wasn’t getting copied into the &lt;code&gt;_build&lt;&#x2F;code&gt; directory which meant that
Cargo was ignoring the vendored libraries.
It turns out that directories beginning with a &lt;code&gt;.&lt;&#x2F;code&gt; or &lt;code&gt;_&lt;&#x2F;code&gt; are ignored
when recursively copying directories specified with &lt;code&gt;source_tree&lt;&#x2F;code&gt;.
There’s even an &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;ocaml&#x2F;dune&#x2F;issues&#x2F;7135&quot;&gt;issue on Dune’s github&lt;&#x2F;a&gt;
where others have run into the same problem.&lt;&#x2F;p&gt;
&lt;p&gt;The documentation for &lt;code&gt;source_tree&lt;&#x2F;code&gt; doesn’t mention this behaviour
because it’s just the default behaviour for which files in a directory
are ignored in general (it affects more that just &lt;code&gt;source_tree&lt;&#x2F;code&gt;). This behaviour
can be adjusted by placing a &lt;code&gt;dune&lt;&#x2F;code&gt; file inside the Rust project with
contents:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;dune&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-dune &quot;&gt;&lt;code class=&quot;language-dune&quot; data-lang=&quot;dune&quot;&gt;&lt;span&gt;(dirs :standard .cargo)
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;…which adds the &lt;code&gt;.cargo&lt;&#x2F;code&gt; directory to the default set of directories to not ignore.&lt;&#x2F;p&gt;
&lt;p&gt;I understand wanting to avoid copying some hidden directories, such as
&lt;code&gt;.git&lt;&#x2F;code&gt; or &lt;code&gt;_build&lt;&#x2F;code&gt;. My issue with this UX is that if you’re learning
Dune by using it and reading the docs for the relevant section as you
go, I don’t see how a situation like mine could have been
avoided. There’s a layer of indirection between specifying a directory
dependency with &lt;code&gt;source_tree&lt;&#x2F;code&gt; and configuring which directories are
copied to &lt;code&gt;_build&lt;&#x2F;code&gt; with the &lt;code&gt;(dirs ...)&lt;&#x2F;code&gt; stanza and unless you’re
already well versed in Dune you won’t realize that if you need to copy
a file beginning with &lt;code&gt;.&lt;&#x2F;code&gt; or &lt;code&gt;_&lt;&#x2F;code&gt; you need to configure Dune to allow
it. I suspect many people who try to include a Rust library inside a
Dune project run into this problem, then check the docs for
&lt;code&gt;source_tree&lt;&#x2F;code&gt; and find no useful information, and get stuck.&lt;&#x2F;p&gt;
&lt;p&gt;As a response to this I’ve added a &lt;a href=&quot;https:&#x2F;&#x2F;dune.readthedocs.io&#x2F;en&#x2F;stable&#x2F;faq.html#files-and-directories-whose-names-begin-with-period-are-ignored-by-source-tree&quot;&gt;section to Dune’s
FAQ&lt;&#x2F;a&gt;
about this specific issue so hopefully when people get stuck on this
issue in the future they can find help online.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;the-obvious-choice-of-package-for-reading-wav-files-crashes-when-reading-wav-files&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#the-obvious-choice-of-package-for-reading-wav-files-crashes-when-reading-wav-files&quot; aria-label=&quot;Anchor link for: the-obvious-choice-of-package-for-reading-wav-files-crashes-when-reading-wav-files&quot;&gt;The obvious choice of package for reading &lt;code&gt;.wav&lt;&#x2F;code&gt; files crashes when reading &lt;code&gt;.wav&lt;&#x2F;code&gt; files&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;I wanted to load some audio samples from &lt;code&gt;.wav&lt;&#x2F;code&gt; files and decided to try out
&lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;savonet&#x2F;ocaml-mm&quot;&gt;ocaml-mm&lt;&#x2F;a&gt; which seemed like the
obvious choice for working with media files. To learn its API I wrote
some code that reads a &lt;code&gt;.wav&lt;&#x2F;code&gt; file and prints its sample rate:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;ocaml&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-ocaml &quot;&gt;&lt;code class=&quot;language-ocaml&quot; data-lang=&quot;ocaml&quot;&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;let &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;() &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=
&lt;&#x2F;span&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;let &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;wav_file &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= new &lt;&#x2F;span&gt;&lt;span&gt;Mm.Audio.IO.Reader.of_wav_file &lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;.&#x2F;cymbal.wav&amp;quot; &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;in
&lt;&#x2F;span&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;let &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;sample_rate &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; wav_file#sample_rate &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;in
&lt;&#x2F;span&gt;&lt;span&gt;  print_endline (Printf.sprintf &lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;sample_rate: %d&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt; sample_rate)
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;It printed &lt;code&gt;sample_rate: 44100&lt;&#x2F;code&gt; as expected. Next let’s read those samples from the file:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;ocaml&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-ocaml &quot;&gt;&lt;code class=&quot;language-ocaml&quot; data-lang=&quot;ocaml&quot;&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;let &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;() &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=
&lt;&#x2F;span&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;let &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;wav_file &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= new &lt;&#x2F;span&gt;&lt;span&gt;Mm.Audio.IO.Reader.of_wav_file &lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;.&#x2F;cymbal.wav&amp;quot; &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;in
&lt;&#x2F;span&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;let &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;buffer &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span&gt;Mm.Audio.create &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;2 10000
&lt;&#x2F;span&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; _ &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; wav_file#read buffer &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;0 1 &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;in
&lt;&#x2F;span&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;()
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This didn’t work:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;Fatal error: exception File &amp;quot;src&#x2F;audio.ml&amp;quot;, line 1892, characters 21-27: Assertion failed
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;That failed assertion is:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;ocaml&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-ocaml &quot;&gt;&lt;code class=&quot;language-ocaml&quot; data-lang=&quot;ocaml&quot;&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;match&lt;&#x2F;span&gt;&lt;span&gt; sample_size &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;with
&lt;&#x2F;span&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;| &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;16 &lt;&#x2F;span&gt;&lt;span&gt;-&amp;gt; S16LE.to_audio sbuf &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt; buf ofs len
&lt;&#x2F;span&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;| &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;8 &lt;&#x2F;span&gt;&lt;span&gt;-&amp;gt; U8.to_audio sbuf &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt; buf ofs len
&lt;&#x2F;span&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;| &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;_ &lt;&#x2F;span&gt;&lt;span&gt;-&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;assert &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;false
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;My test file used 24-bit samples but I can probably live with 16-bit samples:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;$ ffmpeg -i cymbal.wav -af &amp;quot;aformat=s16:sample_rates=44100&amp;quot; cymbal-16bit.wav
&lt;&#x2F;span&gt;&lt;span&gt;$ file cymbal-16bit.wav
&lt;&#x2F;span&gt;&lt;span&gt;cymbal-16bit.wav: RIFF (little-endian) data, WAVE audio, Microsoft PCM, 16 bit, mono 44100 Hz
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;After updating the code to load the new file:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;Fatal error: exception Mm_audio.Audio.IO.Invalid_file
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;At this point I gave up on &lt;code&gt;mm&lt;&#x2F;code&gt; and solved the problem in Rust
instead. I developed the first version of my synthesizer library
during a hackathon and didn’t have time to debug &lt;code&gt;mm&lt;&#x2F;code&gt;. I’d
already done the work to set up Rust interoperability for this project
so it was very quick to extend my Rust library &lt;code&gt;low_level&lt;&#x2F;code&gt; to read
&lt;code&gt;.wav&lt;&#x2F;code&gt; files using the Rust library &lt;a href=&quot;https:&#x2F;&#x2F;crates.io&#x2F;crates&#x2F;hound&quot;&gt;hound&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;This worked well except I ran into an issue copying the audio data from Rust to OCaml because…&lt;&#x2F;p&gt;
&lt;h3 id=&quot;transferring-an-array-of-floats-from-rust-to-ocaml-produced-a-broken-array-this-is-now-fixed&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#transferring-an-array-of-floats-from-rust-to-ocaml-produced-a-broken-array-this-is-now-fixed&quot; aria-label=&quot;Anchor link for: transferring-an-array-of-floats-from-rust-to-ocaml-produced-a-broken-array-this-is-now-fixed&quot;&gt;Transferring an array of floats from Rust to OCaml produced a broken array (this is now fixed!)&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;While adding &lt;code&gt;.wav&lt;&#x2F;code&gt; support to my Rust library I ran into an
interesting bug in &lt;a href=&quot;https:&#x2F;&#x2F;crates.io&#x2F;crates&#x2F;ocaml&quot;&gt;&lt;code&gt;ocaml-rs&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; - the
Rust library for ergonomically calling from OCaml into Rust. My
library reads all the samples from a &lt;code&gt;.wav&lt;&#x2F;code&gt; file and makes them
available to OCaml as a &lt;code&gt;float array&lt;&#x2F;code&gt;, but I was noticing that when
accessing the array in OCaml, all the values were zero.&lt;&#x2F;p&gt;
&lt;p&gt;A simple repro for this bug is this Rust function:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span&gt;#[ocaml::func]
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;pub fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;make_float_array&lt;&#x2F;span&gt;&lt;span&gt;() -&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Vec&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;f32&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;    vec![&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;0.0&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;1.0&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;2.0&lt;&#x2F;span&gt;&lt;span&gt;]
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Thanks to &lt;code&gt;ocaml-rs&lt;&#x2F;code&gt; magic this can be referred to in OCaml as:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;ocaml&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-ocaml &quot;&gt;&lt;code class=&quot;language-ocaml&quot; data-lang=&quot;ocaml&quot;&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;external &lt;&#x2F;span&gt;&lt;span&gt;make_float_array : &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;unit &lt;&#x2F;span&gt;&lt;span&gt;-&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;float array = &lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;make_float_array&amp;quot;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;I found that I could iterate over this array with functions like
&lt;code&gt;Array.to_list&lt;&#x2F;code&gt; and it would work as expected but if I directly
accessed an element of the array with &lt;code&gt;Array.get&lt;&#x2F;code&gt; the result would
always be zero.&lt;&#x2F;p&gt;
&lt;p&gt;OCaml has a special way of representing arrays of floats in
memory. Usually floats are boxed in OCaml, but when they appear in an
array they are unboxed and packed contiguously in memory. &lt;code&gt;ocaml-rs&lt;&#x2F;code&gt;
was handling this correctly for double-precision floats but not for
single-precision floats. I made a
&lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;zshipko&#x2F;ocaml-rs&#x2F;pull&#x2F;144&quot;&gt;PR&lt;&#x2F;a&gt; and the bug is now
fixed.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;adding-inline-tests-to-a-library-requires-adding-over-20-runtime-dependencies&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#adding-inline-tests-to-a-library-requires-adding-over-20-runtime-dependencies&quot; aria-label=&quot;Anchor link for: adding-inline-tests-to-a-library-requires-adding-over-20-runtime-dependencies&quot;&gt;Adding inline tests to a library requires adding over 20 (runtime) dependencies&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;I wanted a MIDI parser so I could &lt;a href=&quot;https:&#x2F;&#x2F;www.youtube.com&#x2F;watch?v=A8a1Dem2eKs&quot;&gt;play other people’s songs on my
synth&lt;&#x2F;a&gt; and I elected to
write my own rather than chance the one in &lt;code&gt;ocaml-mm&lt;&#x2F;code&gt; (fool me once,
etc). This turned out to be really interesting and I ended up
publishing a &lt;a href=&quot;https:&#x2F;&#x2F;ocaml.org&#x2F;p&#x2F;llama_midi&#x2F;latest&quot;&gt;standalone library just for parsing MIDI
data&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;MIDI encodes integers in a variable number of bytes with a special
value denoting the final byte of the integer (kind of like strings in
C). This was a little complicated so I wrote some tests:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;ocaml&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-ocaml &quot;&gt;&lt;code class=&quot;language-ocaml&quot; data-lang=&quot;ocaml&quot;&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;let &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;parse_midi_int &lt;&#x2F;span&gt;&lt;span&gt;a i &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; ...
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;let%&lt;&#x2F;span&gt;&lt;span&gt;test_module _ &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=
&lt;&#x2F;span&gt;&lt;span&gt;  (&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;module struct
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;(* Run the parser on an array of ints. *)
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;let &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;make &lt;&#x2F;span&gt;&lt;span&gt;ints &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=
&lt;&#x2F;span&gt;&lt;span&gt;      run parse_midi_int (Array.map char_of_int (Array.of_list ints))
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;let%&lt;&#x2F;span&gt;&lt;span&gt;test _ &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span&gt;Int.equal &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;0 &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;@@&lt;&#x2F;span&gt;&lt;span&gt; make [ &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;0 &lt;&#x2F;span&gt;&lt;span&gt;]
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;let%&lt;&#x2F;span&gt;&lt;span&gt;test _ &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span&gt;Int.equal &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;0x40 &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;@@&lt;&#x2F;span&gt;&lt;span&gt; make [ &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;0x40 &lt;&#x2F;span&gt;&lt;span&gt;]
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;let%&lt;&#x2F;span&gt;&lt;span&gt;test _ &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span&gt;Int.equal &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;0x2000 &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;@@&lt;&#x2F;span&gt;&lt;span&gt; make [ &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;0xC0&lt;&#x2F;span&gt;&lt;span&gt;; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;0x00 &lt;&#x2F;span&gt;&lt;span&gt;]
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;let%&lt;&#x2F;span&gt;&lt;span&gt;test _ &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span&gt;Int.equal &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;0x1FFFFF &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;@@&lt;&#x2F;span&gt;&lt;span&gt; make [ &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;0xFF&lt;&#x2F;span&gt;&lt;span&gt;; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;0xFF&lt;&#x2F;span&gt;&lt;span&gt;; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;0x7F &lt;&#x2F;span&gt;&lt;span&gt;]
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;let%&lt;&#x2F;span&gt;&lt;span&gt;test _ &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span&gt;Int.equal &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;0x200000 &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;@@&lt;&#x2F;span&gt;&lt;span&gt; make [ &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;0x81&lt;&#x2F;span&gt;&lt;span&gt;; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;0x80&lt;&#x2F;span&gt;&lt;span&gt;; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;0x80&lt;&#x2F;span&gt;&lt;span&gt;; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;0x00 &lt;&#x2F;span&gt;&lt;span&gt;]
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;let%&lt;&#x2F;span&gt;&lt;span&gt;test _ &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span&gt;Int.equal &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;0xFFFFFFF &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;@@&lt;&#x2F;span&gt;&lt;span&gt; make [ &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;0xFF&lt;&#x2F;span&gt;&lt;span&gt;; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;0xFF&lt;&#x2F;span&gt;&lt;span&gt;; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;0xFF&lt;&#x2F;span&gt;&lt;span&gt;; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;0x7F &lt;&#x2F;span&gt;&lt;span&gt;]
&lt;&#x2F;span&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;end&lt;&#x2F;span&gt;&lt;span&gt;)
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;These tests were defined right next to the logic for parsing MIDI
ints. I did it this way so that I wouldn’t need to expose the int
parser outside this module and to make it easy to look at the code and
the tests at the same time.&lt;&#x2F;p&gt;
&lt;p&gt;I followed
&lt;a href=&quot;https:&#x2F;&#x2F;dune.readthedocs.io&#x2F;en&#x2F;stable&#x2F;tests.html#inline-tests&quot;&gt;Dune’s documentation&lt;&#x2F;a&gt;
for writing tests with
&lt;a href=&quot;https:&#x2F;&#x2F;ocaml.org&#x2F;p&#x2F;ppx_inline_test&#x2F;latest&quot;&gt;&lt;code&gt;ppx_inline_test&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;
and it worked well. As I now need the &lt;code&gt;ppx_inline_test&lt;&#x2F;code&gt; package to run my tests, I added it to my project’s dependencies:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt; &amp;quot;ppx_inline_test&amp;quot; {with-test}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The &lt;code&gt;{with-test}&lt;&#x2F;code&gt; tells Opam that this dependency is only needed to
build the package’s tests - not to build the package itself.&lt;&#x2F;p&gt;
&lt;p&gt;The next time I tried installing my library I got an unexpected error:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;File &amp;quot;test&#x2F;dune&amp;quot;, line 6, characters 7-22:
&lt;&#x2F;span&gt;&lt;span&gt;6 |   (pps ppx_inline_test))
&lt;&#x2F;span&gt;&lt;span&gt;           ^^^^^^^^^^^^^^^
&lt;&#x2F;span&gt;&lt;span&gt;Error: Library &amp;quot;ppx_inline_test&amp;quot; not found.
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The machine I was using didn’t have the &lt;code&gt;ppx_inline_test&lt;&#x2F;code&gt; package
installed but I wasn’t trying to run my tests - just install the
library. It turns out that packages that do pre-processing like
&lt;code&gt;ppx_inline_test&lt;&#x2F;code&gt; cannot be marked as &lt;code&gt;with-test&lt;&#x2F;code&gt;; they must be
unconditional dependencies. This is because preprocessor directives
like &lt;code&gt;let%test&lt;&#x2F;code&gt; are not valid OCaml syntax, and the OCaml compiler is
unable to parse the files until an external preprocessor has processed
the files to remove all the preprocessor directives.&lt;&#x2F;p&gt;
&lt;p&gt;I was hesitant to make &lt;code&gt;ppx_inline_test&lt;&#x2F;code&gt; an unconditional dependency
of my MIDI parsing library because my library currently didn’t have any
dependencies at all. From a supply-chain security point of view and also in
my endless pursuit of minimalism it seemed  shame to depend on
&lt;code&gt;ppx_inline_test&lt;&#x2F;code&gt; unconditionally, since the transitive dependency
closure of &lt;code&gt;ppx_inline_test&lt;&#x2F;code&gt; is over 20 packages:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;base
&lt;&#x2F;span&gt;&lt;span&gt;csexp
&lt;&#x2F;span&gt;&lt;span&gt;dune-configurator
&lt;&#x2F;span&gt;&lt;span&gt;jane-street-headers
&lt;&#x2F;span&gt;&lt;span&gt;jst-config
&lt;&#x2F;span&gt;&lt;span&gt;ocaml-compiler-libs
&lt;&#x2F;span&gt;&lt;span&gt;ppx_assert
&lt;&#x2F;span&gt;&lt;span&gt;ppx_base
&lt;&#x2F;span&gt;&lt;span&gt;ppx_cold
&lt;&#x2F;span&gt;&lt;span&gt;ppx_compare
&lt;&#x2F;span&gt;&lt;span&gt;ppx_derivers
&lt;&#x2F;span&gt;&lt;span&gt;ppx_enumerate
&lt;&#x2F;span&gt;&lt;span&gt;ppx_globalize
&lt;&#x2F;span&gt;&lt;span&gt;ppx_hash
&lt;&#x2F;span&gt;&lt;span&gt;ppx_here
&lt;&#x2F;span&gt;&lt;span&gt;ppx_inline_test
&lt;&#x2F;span&gt;&lt;span&gt;ppx_optcomp
&lt;&#x2F;span&gt;&lt;span&gt;ppx_sexp_conv
&lt;&#x2F;span&gt;&lt;span&gt;ppxlib
&lt;&#x2F;span&gt;&lt;span&gt;sexplib0
&lt;&#x2F;span&gt;&lt;span&gt;stdio
&lt;&#x2F;span&gt;&lt;span&gt;stdlib-shims
&lt;&#x2F;span&gt;&lt;span&gt;time_now
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;That’s a lot to download and build just to get the logic for disabling
~10 lines of tests in a MIDI parser.&lt;&#x2F;p&gt;
&lt;p&gt;In the end I did what many libraries do and just exposed the internals
of my MIDI parser in its public interface inside of a module named
&lt;code&gt;For_test&lt;&#x2F;code&gt;, and then added a separate package that depends on both my
MIDI parser and &lt;code&gt;ppx_inline_test&lt;&#x2F;code&gt; and moved the tests there.&lt;&#x2F;p&gt;
&lt;p&gt;I’m used to Rust where I could have written:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;parse_midi_int&lt;&#x2F;span&gt;&lt;span&gt;(...) { &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;... &lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;#[test]
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;test_parse_midi_int &lt;&#x2F;span&gt;&lt;span&gt;() { &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;... &lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;…and any external libraries used in the test only need to be
installed when running the test.&lt;&#x2F;p&gt;
&lt;p&gt;This is easier to do in Rust than in OCaml because Cargo is a build
system, package manager, &lt;em&gt;and&lt;&#x2F;em&gt; preprocessor, so it can impose a syntax
for denoting test code, remove tests when compiling code normally,
and only require test dependencies when actually running the
tests. This is harder in OCaml because preprocessing is handled by
external programs. Neither Dune nor the OCaml compiler itself know
what to do with preprocessor directives and require external packages
to be installed just to know how to ignore them.&lt;&#x2F;p&gt;
&lt;p&gt;I think there’s an opportunity to reduce some of the friction around
testing in OCaml and Dune, especially since security is one of the main selling
points of OCaml. The lower the barrier for writing tests, the more
tests people will write.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;dune-can-generate-opam-files-but-requires-a-workaround-for-adding-the-available-field&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#dune-can-generate-opam-files-but-requires-a-workaround-for-adding-the-available-field&quot; aria-label=&quot;Anchor link for: dune-can-generate-opam-files-but-requires-a-workaround-for-adding-the-available-field&quot;&gt;Dune can generate &lt;code&gt;.opam&lt;&#x2F;code&gt; files but requires a workaround for adding the &lt;code&gt;available&lt;&#x2F;code&gt; field&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;My library only works on &lt;code&gt;x86_64&lt;&#x2F;code&gt; and &lt;code&gt;arm64&lt;&#x2F;code&gt; architectures, I think
because of its Rust dependencies (I haven’t really investigated this).
OCaml is supported on many architectures, so to prevent llama from
being installed on incompatible computers, I manually added this line the
package manifest that I released to the Opam repository:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;diff&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-diff &quot;&gt;&lt;code class=&quot;language-diff&quot; data-lang=&quot;diff&quot;&gt;&lt;span&gt; ...
&lt;&#x2F;span&gt;&lt;span&gt; license: &amp;quot;MIT&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt; homepage: &amp;quot;https:&#x2F;&#x2F;github.com&#x2F;gridbugs&#x2F;llama&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt; bug-reports: &amp;quot;https:&#x2F;&#x2F;github.com&#x2F;gridbugs&#x2F;llama&#x2F;issues&amp;quot;
&lt;&#x2F;span&gt;&lt;span style=&quot;background-color:#eaffea;font-weight:bold;color:#55a532;&quot;&gt;+&lt;&#x2F;span&gt;&lt;span style=&quot;background-color:#eaffea;color:#323232;&quot;&gt;available: arch != &amp;quot;arm32&amp;quot; &amp;amp; arch != &amp;quot;ppc64&amp;quot; &amp;amp; arch != &amp;quot;s390x&amp;quot; &amp;amp; arch != &amp;quot;x86_32&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt; depends: [
&lt;&#x2F;span&gt;&lt;span&gt;   &amp;quot;dune&amp;quot; {&amp;gt;= &amp;quot;3.0&amp;quot;}
&lt;&#x2F;span&gt;&lt;span&gt;   &amp;quot;llama_core&amp;quot; {= version}
&lt;&#x2F;span&gt;&lt;span&gt; ...
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Llama’s Opam manifests are generated by Dune. The metadata in its Opam
manifests are present in its &lt;code&gt;dune-project&lt;&#x2F;code&gt; file as:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;dune&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-dune &quot;&gt;&lt;code class=&quot;language-dune&quot; data-lang=&quot;dune&quot;&gt;&lt;span&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;source&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;github&lt;&#x2F;span&gt;&lt;span&gt; gridbugs&#x2F;llama))
&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;license&lt;&#x2F;span&gt;&lt;span&gt; MIT)
&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;package
&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;name&lt;&#x2F;span&gt;&lt;span&gt; llama)
&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;synopsis &lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;Language for Live Audio Module Arrangement&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)
&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;description &lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;Libraries for declaratively building software-defined modular synthesizers&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)
&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;depends
&lt;&#x2F;span&gt;&lt;span&gt;  (llama_core (&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; :version))
&lt;&#x2F;span&gt;&lt;span&gt;  ...
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This looked to me like a one-to-one translation from the &lt;code&gt;dune-project&lt;&#x2F;code&gt; file to the Opam package manifest, so I assumed I could add an &lt;code&gt;available&lt;&#x2F;code&gt; field to the package’s description like:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;dune&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-dune &quot;&gt;&lt;code class=&quot;language-dune&quot; data-lang=&quot;dune&quot;&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;package
&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;name&lt;&#x2F;span&gt;&lt;span&gt; llama)
&lt;&#x2F;span&gt;&lt;span&gt; (available (&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;and&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;lt;&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt; :arch arm32) (&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;lt;&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt; :arch ppc64) (&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;lt;&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt; :arch s390x) (&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;lt;&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt; :arch x86_32)))
&lt;&#x2F;span&gt;&lt;span&gt; ...
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;But this is not supported:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;File &amp;quot;dune-project&amp;quot;, line 31, characters 2-11:
&lt;&#x2F;span&gt;&lt;span&gt;31 |  (available (and (&amp;lt;&amp;gt; :arch arm32) (&amp;lt;&amp;gt; :arch ppc64) (&amp;lt;&amp;gt; :arch s390x) (&amp;lt;&amp;gt; :arch x86_32)))
&lt;&#x2F;span&gt;&lt;span&gt;Error: Unknown field available
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;I found this surprising as it really seemed like it was a one-to-one
translation. It felt like Dune’s UI had trained me to expect that it
works a certain way, only then to reveal that it actually works a
different way. The &lt;a href=&quot;https:&#x2F;&#x2F;dune.readthedocs.io&#x2F;en&#x2F;stable&#x2F;howto&#x2F;opam-file-generation.html&quot;&gt;docs for generating Opam
files&lt;&#x2F;a&gt;
don’t specify that the &lt;code&gt;available&lt;&#x2F;code&gt; field is supported and at first I
thought it was just not yet implemented. There is a &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;ocaml&#x2F;dune&#x2F;issues&#x2F;7059&quot;&gt;github
issue&lt;&#x2F;a&gt; to support
additional fields, but from the discussion there it’s apparent that the
missing fields are omitted intentionally. There is a policy of not
adding fields that are only used by Opam, and Dune doesn’t currently
have an analog of this feature.&lt;&#x2F;p&gt;
&lt;p&gt;There is a workaround to add the &lt;code&gt;available&lt;&#x2F;code&gt; field to
the generated Opam file using an “Opam Template”. If I had read the
&lt;a href=&quot;https:&#x2F;&#x2F;dune.readthedocs.io&#x2F;en&#x2F;stable&#x2F;howto&#x2F;opam-file-generation.html&quot;&gt;generating Opam files
docs&lt;&#x2F;a&gt;
more carefully I would have noticed:&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;(package)&lt;&#x2F;code&gt; stanzas do not support all opam fields or complete
syntax for dependency specifications. If the package you are
adapting requires this, keep the corresponding opam fields in a
&lt;code&gt;pkg.opam.template&lt;&#x2F;code&gt; file.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;So I just ended up making a &lt;code&gt;llama.opam.template&lt;&#x2F;code&gt; file with the contents:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;available: arch != &amp;quot;arm32&amp;quot; &amp;amp; arch != &amp;quot;ppc64&amp;quot; &amp;amp; arch != &amp;quot;s390x&amp;quot; &amp;amp; arch != &amp;quot;x86_32&amp;quot;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h3 id=&quot;if-some-but-not-all-of-the-interdependent-packages-in-a-project-are-released-opam-can-t-solve-the-project-s-dependencies&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#if-some-but-not-all-of-the-interdependent-packages-in-a-project-are-released-opam-can-t-solve-the-project-s-dependencies&quot; aria-label=&quot;Anchor link for: if-some-but-not-all-of-the-interdependent-packages-in-a-project-are-released-opam-can-t-solve-the-project-s-dependencies&quot;&gt;If some (but not all) of the interdependent packages in a project are released, Opam can’t solve the project’s dependencies&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;I released my synthesizer library on Opam. It was made up of 3 packages:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;llama_core&lt;&#x2F;code&gt; defines the data types for representing audio streams and contains a library of sound effects and filters&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;llama&lt;&#x2F;code&gt; lets you play audio streams through your speakers&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;llama_interactive&lt;&#x2F;code&gt; lets you use your computer’s keyboard and mouse to control the synthesizer&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;There’s also an unreleased package &lt;code&gt;llama_tests&lt;&#x2F;code&gt; that contains all the
tests. As described above, tests are in a separate package to avoid
adding unconditional testing dependencies to other packages.&lt;&#x2F;p&gt;
&lt;p&gt;The MIDI decoder was originally part of &lt;code&gt;llama_core&lt;&#x2F;code&gt; but I wanted to
split it out into a new package &lt;code&gt;llama_midi&lt;&#x2F;code&gt; since it’s useful on its
own outside of the context of the synthesizer library. I created the
new package but didn’t release it to the Opam repository right
away. Around this time I tried setting up the project on a new
computer, and this is when my problems started.&lt;&#x2F;p&gt;
&lt;p&gt;Following the convention for OCaml projects, I put an Opam package manifest for each package in the project in the root directory of the project.&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;$ ls
&lt;&#x2F;span&gt;&lt;span&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;llama.opam
&lt;&#x2F;span&gt;&lt;span&gt;llama_core.opam
&lt;&#x2F;span&gt;&lt;span&gt;llama_interactive.opam
&lt;&#x2F;span&gt;&lt;span&gt;llama_midi.opam
&lt;&#x2F;span&gt;&lt;span&gt;llama_tests.opam
&lt;&#x2F;span&gt;&lt;span&gt;...
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;On my new computer I want to install all the dependencies of all the packages in this directory which will allow me to build the project with Dune.&lt;&#x2F;p&gt;
&lt;p&gt;Opam supports running &lt;code&gt;opam install &amp;lt;dir&amp;gt;&lt;&#x2F;code&gt; which will install all the
packages with package manifests in &lt;code&gt;&amp;lt;dir&amp;gt;&lt;&#x2F;code&gt;, along with all their
dependencies. If you just want the dependencies and not the packages
themselves you can pass &lt;code&gt;--deps-only&lt;&#x2F;code&gt;. To set up the project on my new
computer, I thought the right thing to do would be running &lt;code&gt;opam install . --deps-only&lt;&#x2F;code&gt; from the root directory of the project.&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;$ opam install . --deps-only
&lt;&#x2F;span&gt;&lt;span&gt;[ERROR] Package conflict!
&lt;&#x2F;span&gt;&lt;span&gt;  * Missing dependency:
&lt;&#x2F;span&gt;&lt;span&gt;    - llama_midi &amp;gt;= 0.0.1
&lt;&#x2F;span&gt;&lt;span&gt;    no matching version
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;No solution found, exiting
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This was frustrating as &lt;code&gt;llama_midi.opam&lt;&#x2F;code&gt; was right there. Remember
that &lt;code&gt;llama_midi&lt;&#x2F;code&gt; was a new package, and not yet released to the Opam
repo. Maybe Opam was trying to find &lt;code&gt;llama_midi&lt;&#x2F;code&gt; in the Opam repo and
failing because it’s not released yet.&lt;&#x2F;p&gt;
&lt;p&gt;Next I tried running &lt;code&gt;opam pin&lt;&#x2F;code&gt;. This does a similar thing to &lt;code&gt;opam install&lt;&#x2F;code&gt; except it associates local packages with the path to their
source code on disk. This shouldn’t be necessary in this case; I don’t
even want to install &lt;code&gt;llama_midi&lt;&#x2F;code&gt; - just the dependencies of my local
packages. I tried it anyway:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;$ opam pin .
&lt;&#x2F;span&gt;&lt;span&gt;This will pin the following packages: llama, llama_core, llama_interactive,
&lt;&#x2F;span&gt;&lt;span&gt;llama_midi, llama_tests. Continue? [Y&#x2F;n] Y
&lt;&#x2F;span&gt;&lt;span&gt;Processing  3&#x2F;5: [llama: git] [llama_core: git] [llama_interactive: git]
&lt;&#x2F;span&gt;&lt;span&gt;llama is now pinned to git+file:&#x2F;&#x2F;&#x2F;...&#x2F;llama#main (version 0.0.1)
&lt;&#x2F;span&gt;&lt;span&gt;llama_core is now pinned to git+file:&#x2F;&#x2F;&#x2F;...&#x2F;llama#main (version 0.0.1)
&lt;&#x2F;span&gt;&lt;span&gt;llama_interactive is now pinned to git+file:&#x2F;&#x2F;&#x2F;...&#x2F;llama#main (version 0.0.1)
&lt;&#x2F;span&gt;&lt;span&gt;Package llama_midi does not exist, create as a NEW package? [Y&#x2F;n] Y
&lt;&#x2F;span&gt;&lt;span&gt;llama_midi is now pinned to git+file:&#x2F;&#x2F;&#x2F;...&#x2F;llama#main (version ~dev)
&lt;&#x2F;span&gt;&lt;span&gt;Package llama_tests does not exist, create as a NEW package? [Y&#x2F;n] Y
&lt;&#x2F;span&gt;&lt;span&gt;llama_tests is now pinned to git+file:&#x2F;&#x2F;&#x2F;...&#x2F;llama#main (version ~dev)
&lt;&#x2F;span&gt;&lt;span&gt;[ERROR] Package conflict!
&lt;&#x2F;span&gt;&lt;span&gt;  * Missing dependency:
&lt;&#x2F;span&gt;&lt;span&gt;    - llama_midi &amp;gt;= 0.0.1
&lt;&#x2F;span&gt;&lt;span&gt;    no matching version
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;[NOTE] Pinning command successful, but your installed packages may be out of sync.
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Same error as before, only this time it happened to print the version
number of each pinned package. Notice how for the three released
packages it says &lt;code&gt;(version 0.0.1)&lt;&#x2F;code&gt; but for the unreleased &lt;code&gt;llama_midi&lt;&#x2F;code&gt;
and &lt;code&gt;llama_tests&lt;&#x2F;code&gt; it says &lt;code&gt;(version ~dev)&lt;&#x2F;code&gt;. This tells me that Opam is
trying to install &lt;code&gt;0.0.1&lt;&#x2F;code&gt; of all the released packages, and version
&lt;code&gt;~dev&lt;&#x2F;code&gt; of the unreleased packages. &lt;code&gt;0.0.1&lt;&#x2F;code&gt; happens to be the version
number I used when releasing &lt;code&gt;llama_core&lt;&#x2F;code&gt;, &lt;code&gt;llama&lt;&#x2F;code&gt;, and
&lt;code&gt;llama_interactive&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Since the initial release, I split &lt;code&gt;llama_midi&lt;&#x2F;code&gt; out of &lt;code&gt;llama_core&lt;&#x2F;code&gt;,
and made &lt;code&gt;llama_core&lt;&#x2F;code&gt; depend on the new &lt;code&gt;llama_midi&lt;&#x2F;code&gt; package. I want
to keep the versions of all the llama packages tied together, so when
one of the llama packages depends on another, I declare that
dependency as:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;# llama_core.opam
&lt;&#x2F;span&gt;&lt;span&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;depends: [
&lt;&#x2F;span&gt;&lt;span&gt;  &amp;quot;llama_midi&amp;quot; {= version}
&lt;&#x2F;span&gt;&lt;span&gt;  ...
&lt;&#x2F;span&gt;&lt;span&gt;]
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The &lt;code&gt;{= version}&lt;&#x2F;code&gt; tells Opam that any given version of &lt;code&gt;llama_core&lt;&#x2F;code&gt;
depends on the &lt;code&gt;llama_midi&lt;&#x2F;code&gt; package with the identical version
number. And the error I’m seeing is because Opam is trying to install
version &lt;code&gt;0.0.1&lt;&#x2F;code&gt; of &lt;code&gt;llama_core&lt;&#x2F;code&gt;, but since &lt;code&gt;llama_midi&lt;&#x2F;code&gt; has never been
released, the only version Opam knows about is the synthetic version
number &lt;code&gt;~dev&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;But why was Opam installing version &lt;code&gt;0.0.1&lt;&#x2F;code&gt; of &lt;code&gt;llama_core&lt;&#x2F;code&gt; in the
first place?  Unlike some (most?) other package managers, Opam package
manifests don’t contain the version number of the package they
describe. The only place where package version numbers are recorded is
as part of a directory name inside the Opam package repository, where
manifests are stored in directories named like &lt;code&gt;&amp;lt;package&amp;gt;.&amp;lt;version&amp;gt;&lt;&#x2F;code&gt;
(&lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;ocaml&#x2F;opam-repository&#x2F;tree&#x2F;master&#x2F;packages&#x2F;llama_core&quot;&gt;for
example&lt;&#x2F;a&gt;
&lt;code&gt;llama_core.0.0.1&lt;&#x2F;code&gt;).  My expectation was that since it’s being
installed as a local package from a local Opam package manifest file,
&lt;code&gt;llama_core&lt;&#x2F;code&gt; would be given the version number &lt;code&gt;~dev&lt;&#x2F;code&gt;. The local
package file is clearly being read because Opam is trying to respect
the fact that &lt;code&gt;llama_core&lt;&#x2F;code&gt; now depends on &lt;code&gt;llama_midi&lt;&#x2F;code&gt;, but Opam is
still using the version number of the released version of
&lt;code&gt;llama_core&lt;&#x2F;code&gt;: &lt;code&gt;0.0.1&lt;&#x2F;code&gt;. That version number isn’t stored anywhere in
llama’s git repo, so Opam must be using information from its package
repository to choose this version number, and then it can’t solve
dependencies because there is no version of &lt;code&gt;llama_midi&lt;&#x2F;code&gt; with the same
version number.&lt;&#x2F;p&gt;
&lt;p&gt;In other words, if none of the packages in my project had been
released then I wouldn’t have this problem as Opam would consider each
package to have the version number &lt;code&gt;~dev&lt;&#x2F;code&gt;. If all the packages in my
project had been released then I wouldn’t have this problem as Opam
would look up the released versions of the packages in its package
repo and find matching version numbers for each package. This problem
only happens when some but not all of the interdependent packages in a
project have been released.&lt;&#x2F;p&gt;
&lt;p&gt;Maybe I can work around this by forcing Opam to install the
local packages with a specified version number rather than taking the
version numbers from the package repository.
I learnt that it’s possible to override the version number of packages installed with &lt;code&gt;opam pin&lt;&#x2F;code&gt; by passing &lt;code&gt;--with-version&lt;&#x2F;code&gt;.
I tried installing all the local packages with version number &lt;code&gt;sigh&lt;&#x2F;code&gt; as I was getting quite exasperated.&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;opam pin . --with-version sigh
&lt;&#x2F;span&gt;&lt;span&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;#=== ERROR while compiling llama.sigh =========================================#
&lt;&#x2F;span&gt;&lt;span&gt;# context     2.1.5 | macos&#x2F;arm64 | ocaml-base-compiler.4.14.1 | pinned(git+file:&#x2F;&#x2F;&#x2F;...&#x2F;llama#main#06deea42efa6b84653be43529daf8aa08dc106
&lt;&#x2F;span&gt;&lt;span&gt;68)
&lt;&#x2F;span&gt;&lt;span&gt;# path        &#x2F;...&#x2F;_opam&#x2F;.opam-switch&#x2F;build&#x2F;llama.sigh
&lt;&#x2F;span&gt;&lt;span&gt;# command     ~&#x2F;.opam&#x2F;opam-init&#x2F;hooks&#x2F;sandbox.sh build dune build -p llama -j 7 @install
&lt;&#x2F;span&gt;&lt;span&gt;# exit-code   1
&lt;&#x2F;span&gt;&lt;span&gt;# env-file    ~&#x2F;.opam&#x2F;log&#x2F;llama-60822-042f19.env
&lt;&#x2F;span&gt;&lt;span&gt;# output-file ~&#x2F;.opam&#x2F;log&#x2F;llama-60822-042f19.out
&lt;&#x2F;span&gt;&lt;span&gt;### output ###
&lt;&#x2F;span&gt;&lt;span&gt;# error: failed to get `anyhow` as a dependency of package `low_level v0.1.0 (...&#x2F;_opam&#x2F;.opam-switch&#x2F;build&#x2F;llama.sigh&#x2F;_build&#x2F;default&#x2F;src
&lt;&#x2F;span&gt;&lt;span&gt;&#x2F;low-level&#x2F;low-level-rust)`
&lt;&#x2F;span&gt;&lt;span&gt;# [...]
&lt;&#x2F;span&gt;&lt;span&gt;# Caused by:
&lt;&#x2F;span&gt;&lt;span&gt;#   failed to query replaced source registry `crates-io`
&lt;&#x2F;span&gt;&lt;span&gt;#
&lt;&#x2F;span&gt;&lt;span&gt;# Caused by:
&lt;&#x2F;span&gt;&lt;span&gt;#   download of config.json failed
&lt;&#x2F;span&gt;&lt;span&gt;#
&lt;&#x2F;span&gt;&lt;span&gt;# Caused by:
&lt;&#x2F;span&gt;&lt;span&gt;#   failed to download from `https:&#x2F;&#x2F;index.crates.io&#x2F;config.json`
&lt;&#x2F;span&gt;&lt;span&gt;#
&lt;&#x2F;span&gt;&lt;span&gt;# Caused by:
&lt;&#x2F;span&gt;&lt;span&gt;#   [7] Couldn&amp;#39;t connect to server (Failed to connect to index.crates.io port 443 a
&lt;&#x2F;span&gt;&lt;span&gt;fter 0 ms: Couldn&amp;#39;t connect to server)
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;That error is because Cargo is trying to download dependencies while
building the Rust component of the &lt;code&gt;llama&lt;&#x2F;code&gt; package from within Opam’s
build sandbox. Earlier in the post I described vendoring all of the
Rust dependencies as they can’t be downloaded when building the
package with Opam, as Opam’s build sandbox doesn’t have internet
access. This vendoring usually takes place in a build script that runs
as a github action. I don’t check in the vendored libraries as it
would bloat the repo, and when developing locally with Dune there is
no need to vendor them (Dune’s build sandbox &lt;em&gt;does&lt;&#x2F;em&gt; have internet
access).&lt;&#x2F;p&gt;
&lt;p&gt;Recall that I don’t even want to install &lt;code&gt;llama&lt;&#x2F;code&gt; with Opam - I
just want to install all the non-local dependencies of the local
packages that make up my project. There may well be a way to do this
but I couldn’t find it and nobody I asked knew how to do it so I just
ended up installing the dependencies by hand. Fortunately
there were only a few. Eventually I released &lt;code&gt;llama_midi&lt;&#x2F;code&gt; and so this
problem went away.&lt;&#x2F;p&gt;
&lt;p&gt;This was the point where I was really starting to question whether
OCaml was the right tool for this project, and for any other project I
want to develop on my own time.  It’s important to me that I can clone
a project on a machine with OCaml installed on it and be up and
running after a command or two, kind of like &lt;code&gt;npm install&lt;&#x2F;code&gt; or &lt;code&gt;cargo run&lt;&#x2F;code&gt; which in my experience tend to “just work”.  The fact that it was
such a headache to do something that I expected to be simple suggested
to me that the philosophy underpinning the UX of Opam is really
different from the way that I tend to approach software
development. Fortunately this won’t be a problem for much longer as
Dune’s package management features are quickly maturing and are
designed with this use case in mind.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-happy-path&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#the-happy-path&quot; aria-label=&quot;Anchor link for: the-happy-path&quot;&gt;The “Happy Path”&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;I hear from a lot of OCaml developers that tooling works well when you keep to the “Happy
Path” and I tend to agree with this. This refers to the case where all
the code in your project is written in OCaml and you use the default
configurations for everything. Lately I’ve been developing a &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;gridbugs&#x2F;climate&quot;&gt;CLI
parsing library&lt;&#x2F;a&gt; in OCaml which
sticks to the Happy Path. It’s entirely written in OCaml, doesn’t link
with any external libraries, only depends on third-party packages for
its tests and is compatible with all architectures. So far I haven’t had
any issues with tooling, and even been pleasantly surprised a couple
of times.&lt;&#x2F;p&gt;
&lt;p&gt;Most of the negative experiences from this post happened when I
strayed from the Happy Path into parts of the ecosystem that are less
polished and battle tested, or when my assumptions ran contrary to
those made by tools.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;conclusions&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#conclusions&quot; aria-label=&quot;Anchor link for: conclusions&quot;&gt;Conclusions&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;This experience taught me that if you go into an OCaml project
expecting the tools to “just work”, you’re probably going to have a
bad time. Expect that the first few times you try to modify a Dune
build configuration that the syntax will be incorrect, and once that’s
fixed expect the configuration to not do what you wanted
in the first place. Expect third-party packages to be buggy and untested around
edge-cases. Expect to get into confusing situations when using Opam to
manage local packages that you’re actively developing. And expect &lt;a href=&quot;https:&#x2F;&#x2F;en.wiktionary.org&#x2F;wiki&#x2F;yak_shaving&quot;&gt;yak
shaves&lt;&#x2F;a&gt; - so many times
when one thing isn’t working correctly I run into a second, unrelated
issue while trying to resolve the first issue.&lt;&#x2F;p&gt;
&lt;p&gt;Normalize complaining about this stuff so that new OCaml users can
correctly set their expectations coming in and don’t get a nasty shock
the first time they leave the Happy Path. And if you find yourself
struggling with the tools, don’t beat yourself up about it. Most OCaml
users struggle. I clearly struggle. Remember, it’s not you - it’s the
tools.&lt;&#x2F;p&gt;
&lt;p&gt;As for my synth library, due to the friction I experienced developing
it in OCaml, and to avoid the future frustration I anticipated if I
continued the project, &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;gridbugs&#x2F;caw&quot;&gt;I rewrote it in Rust&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Electric Organ</title>
          <pubDate>Tue, 09 Jul 2024 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/electric-organ/</link>
          <guid>https://www.gridbugs.org/electric-organ/</guid>
          <description xml:base="https://www.gridbugs.org/electric-organ/">&lt;p&gt;Electric Organ is a traditional roguelike with an emphasis on ranged
combat. Character progression primarily takes the form of swapping out
your organs as they are damaged by the environment, and upgrading them
to make yourself strong enough to face the threats below the city.&lt;&#x2F;p&gt;
&lt;p&gt;It’s my 9th entry into the &lt;a href=&quot;https:&#x2F;&#x2F;7drl.com&#x2F;&quot;&gt;7 Day Roguelike game jam&lt;&#x2F;a&gt;. It placed 1st according to &lt;a href=&quot;https:&#x2F;&#x2F;roguetemple.com&#x2F;7drl&#x2F;2024&#x2F;&quot;&gt;roguetemple’s leaderboard&lt;&#x2F;a&gt;!&lt;&#x2F;p&gt;
&lt;p&gt;Download or play in a web browser on its &lt;a href=&quot;https:&#x2F;&#x2F;gridbugs.itch.io&#x2F;electric-organ&quot;&gt;itch.io page&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;The game is open source, and its source code can be found &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;gridbugs&#x2F;electric-organ&quot;&gt;here&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;And the devlog I wrote while developing this game begins &lt;a href=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;7drl2024-day1&#x2F;&quot;&gt;here&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;electric-organ&#x2F;cover.png&quot; alt=&quot;The game’s menu screen without the text. A cyan heart in front of a city. The ground shows a magenta grid vanishing towards the distance. The sky is purple and peppered with stars.&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Electric Organ is by far the most complicated thing I have ever
built. It runs on an engine that I’ve been developing over the past 5
or so years, and the music is generated live by a synthesizer library
that I’ve been developing for several months. It features a particle
system, dynamic lighting, and procedural music and sound effects. Some
of this technology I repurposed from previous projects, but getting it
all to integrate together still took a lot of work.&lt;&#x2F;p&gt;
&lt;p&gt;It’s also dense with gameplay systems, the foremost of which is
organs, where the player can add and remove organs from their bodies
for various effects. Additionally, organs can have traits such as
transient which causes the organ to vanish at a random point in the
future, and vampiric which means the organ only functions if you’ve
recently injected a blood vial.&lt;&#x2F;p&gt;
&lt;p&gt;There are vendors which sell organs, guns and ammo, and items. There
are 14 different items with different abilities. There are 9 different
types of enemy, with unique abilities, such as the venter
which emits smoke that makes it hard to breath, the snatcher which
steals items, and the climber which can pass over usually-impassable
debris. And there’s a final boss with several abilities found on
regular enemies, as well as a teleport ability that activates at
certain damage thresholds, which serves to prolong the boss fight.&lt;&#x2F;p&gt;
&lt;p&gt;The combat system is a mix of ranged and melee. The game tracks the
weapon held in each hand. Some weapons require both hands. Plus, you
can install claws (a type of organ) which greatly increases melee
combat but means you can’t hold anything in that hand. Getting hand
tracking to work was probably the most tedious part of the whole
project. The code is a mess, but it works.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;electric-organ&#x2F;screenshot1.png&quot; alt=&quot;Fighting the final boss on the bottom level of the city&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;I had a clear plan for the art style and gameplay going into the
week. I’m currently obsessed with synthesizers and I’m developing a
&lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;gridbugs&#x2F;caw&quot;&gt;rust synthesizer library&lt;&#x2F;a&gt; and wanted
to showcase it in this year’s 7DRL by procedurally generating the
music. The general vibe was largely inspired by the pulpy dystopian
city streets of John Carpenter’s “Escape from New York”, the aesthetic
violent nihilism of the game Hotline Miami 2, and the cyberpunk
setting of Akira.&lt;&#x2F;p&gt;
&lt;p&gt;The core mechanic of swapping out organs was originally inspired by
the artifact system in the Stalker FPS franchise, where the player can
equip magical items which usually had both a positive and negative
status effect. I thought it would be interesting if the player could
equip such items without being able to easily unequip them, so adding
an artifact would be a more significant choice. This original idea has
been muddied somewhat in its implementation in Electric Organ, but the
core mechanic of swapping and upgrading organs fits well with the
game’s aesthetic and I’m still happy with how it turned out. One
criticism I have is that it’s almost never worth it to harvest organs
from the enemies you kill in the game, so I’d be interested in finding
a way to motivate players to do that more.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;electric-organ&#x2F;screenshot3.png&quot; alt=&quot;In a smoke-filled street, the player is aiming a pistol at a Venter enemy.&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;I’m satisfied with how the 7DRL week went. I had an easier time
staying motivated than in the last few 7DRLs and managed to spend
almost every non-work waking hour during the week working on my
game. It feels like I’m hitting up against the limit of the
productivity I can achieve with my current tools. I say this every
year but I do want to keep working on Electric Organ now that the jam
is finished, both because I want to use it to improve my tools (or
experiment with new tools), and because I really like how the game
turned out and want to spend some time making it even better.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;electric-organ&#x2F;screenshot2.png&quot; alt=&quot;Message log death screen after the player attacked a vendor who then turned hostile and killed them&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Once again, you can download or play Electric Organ on its &lt;a href=&quot;https:&#x2F;&#x2F;gridbugs.itch.io&#x2F;electric-organ&quot;&gt;itch.io page&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>7 Day Roguelike 2024: Shops, Organs, Environment Effects, Boss</title>
          <pubDate>Sat, 09 Mar 2024 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/7drl2024-day7/</link>
          <guid>https://www.gridbugs.org/7drl2024-day7/</guid>
          <description xml:base="https://www.gridbugs.org/7drl2024-day7/">&lt;p&gt;It’s the end of day 7 and my game is complete. I stayed up all night finishing
it and managed to complete all the features and content I intended and still
had a few hours for delirious  playtesting and bugfixing. This was by far the
largest scope for any 7DRL I’ve done in the past in terms of features as well
as content. This game has procedurally-generated music and sound effects, 10
types of enemy each with their own unique abilities (not including
shopkeepers), 14 items, an equipment system that allows dual-wielding
one-handed weapons, accounts for two-handed weapons, and allows each hand to
turn into claws that can’t hold weapons (e.g. you can have one claw and still
hold a one-handed weapon in the other hand), a system for determining the
player’s stats based on which organs they currently have, a system for a
applying mutations to organs if you absorb too much radiation, realtime
particle effects for smoke and explosions, a dynamic diminishing lighting
system and a secret ending (shhhh!). All the items have descriptions that render in the
UI, and there are many different types of message that can appear in the game’s
message log about events that happen in the game.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;7drl2024-day7&#x2F;screenshot1.png&quot; alt=&quot;Fighting the final boss on the bottom level of the city&quot; &#x2F;&gt;&lt;&#x2F;p&gt;</description>
      </item>
      <item>
          <title>7 Day Roguelike 2024: Applying Items, Equipment</title>
          <pubDate>Fri, 08 Mar 2024 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/7drl2024-day6/</link>
          <guid>https://www.gridbugs.org/7drl2024-day6/</guid>
          <description xml:base="https://www.gridbugs.org/7drl2024-day6/">&lt;p&gt;One day left! Tonight I implemented the equipment system. Weapons can be equipped
from the inventory to the hands. Some weapons require both hands while others
require only one. This is important as the “Claw” organ takes up a hand; you
can’t use a two-handed weapon if one of your hands is a claw (and you can’t use
a one-handed weapon if both of your hands are claws). Getting the logic and
error handling for equipping, unequipping and reloading weapons was trickier
than I expected. Especially since you can hold one pistol in each hand.&lt;&#x2F;p&gt;
&lt;p&gt;It’s possible to lose the game:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;7drl2024-day6&#x2F;screenshot1.png&quot; alt=&quot;death screen&quot; &#x2F;&gt;&lt;&#x2F;p&gt;</description>
      </item>
      <item>
          <title>7 Day Roguelike 2024: Items</title>
          <pubDate>Thu, 07 Mar 2024 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/7drl2024-day5/</link>
          <guid>https://www.gridbugs.org/7drl2024-day5/</guid>
          <description xml:base="https://www.gridbugs.org/7drl2024-day5/">&lt;p&gt;Tonight I added items. This includes the logic for picking up and dropping
objects, the UI control flow for displaying menus allowing the playing to drop
or use an item, and the item descriptions.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;7drl2024-day5&#x2F;apply-item.png&quot; alt=&quot;Apply item menu&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;The most complicated part is dealing with what happens when a user drops an item
while standing in a cell that already contains an item. The game engine does not
allow multiple items to exist at the same location, so when a collision would
occur the game searches for the nearest cell that doesn’t contain an item
(without traversing wall, etc) and puts the item there.&lt;&#x2F;p&gt;</description>
      </item>
      <item>
          <title>7 Day Roguelike 2024: Ranged Combat, Sound Effects, Message Log</title>
          <pubDate>Wed, 06 Mar 2024 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/7drl2024-day4/</link>
          <guid>https://www.gridbugs.org/7drl2024-day4/</guid>
          <description xml:base="https://www.gridbugs.org/7drl2024-day4/">&lt;p&gt;The main focus of the day was the combat system. This involved setting up the
control flow so that when the player presses the fire button the UI lets them
aim, and then commits the fire action after the user confirms their target.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;7drl2024-day4&#x2F;aim.png&quot; alt=&quot;Gameplay demonstrating the aim UI&quot; &#x2F;&gt;&lt;&#x2F;p&gt;</description>
      </item>
      <item>
          <title>7 Day Roguelike 2024: Title, Pathfinding, UI</title>
          <pubDate>Tue, 05 Mar 2024 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/7drl2024-day3/</link>
          <guid>https://www.gridbugs.org/7drl2024-day3/</guid>
          <description xml:base="https://www.gridbugs.org/7drl2024-day3/">&lt;p&gt;I had a productive evening working on Electric Organ. Firstly I implemented an
animated main menu. I’m spending a lot of time up front working on aesthetics
as I find that if the game is pretty I’m more motivated to work on it later in
the week. Hopefully there’s enough time to implement all the mechanics I have
planned.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;7drl2024-day3&#x2F;menu.png&quot; alt=&quot;Main menu of the game. The foreground is a neon pink grid. The background is a city with a night sky behind it. There is a floating blue anatomical heart.&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;See the animation along with the music on &lt;a href=&quot;https:&#x2F;&#x2F;youtu.be&#x2F;y5wBxGF-kPM&quot;&gt;youtube&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;</description>
      </item>
      <item>
          <title>7 Day Roguelike 2024: Animation, Music, Level Switching</title>
          <pubDate>Mon, 04 Mar 2024 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/7drl2024-day2/</link>
          <guid>https://www.gridbugs.org/7drl2024-day2/</guid>
          <description xml:base="https://www.gridbugs.org/7drl2024-day2/">&lt;p&gt;I mostly spent the day working on dynamically-generated synthesizer music. There
are currently two songs - one for the main menu and another that plays during
levels. If I get time I will add more.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;7drl2024-day2&#x2F;screenshot.png&quot; alt=&quot;Ascii representation of a city with burning debris and a glowing green blob&quot; &#x2F;&gt;&lt;&#x2F;p&gt;</description>
      </item>
      <item>
          <title>7 Day Roguelike 2024: Procgen, Graphics</title>
          <pubDate>Sun, 03 Mar 2024 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/7drl2024-day1/</link>
          <guid>https://www.gridbugs.org/7drl2024-day1/</guid>
          <description xml:base="https://www.gridbugs.org/7drl2024-day1/">&lt;p&gt;It’s the end of day 1 of 7DRL2024. So far I’ve got a level generator for an
area of ruined city and basic graphics and lighting:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;7drl2024-day1&#x2F;screenshot.png&quot; alt=&quot;Screenshot showing a pink street with some burning debris&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;While working on this I fixed a very longstanding bug in my &lt;a href=&quot;https:&#x2F;&#x2F;crates.io&#x2F;crates&#x2F;visible_area_detection&quot;&gt;lighting
system&lt;&#x2F;a&gt; that caused lights to
be too bright near their sources and to drop off with harsh steps rather than a
smooth gradient.&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Component Pinouts and Notes</title>
          <pubDate>Sat, 27 May 2023 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/component-pinouts-and-notes/</link>
          <guid>https://www.gridbugs.org/component-pinouts-and-notes/</guid>
          <description xml:base="https://www.gridbugs.org/component-pinouts-and-notes/">&lt;p&gt;This page will list pinouts of some electronic components I commonly find myself
using, as well as some notes on using them that might not be obvious from
reading the datasheets.&lt;&#x2F;p&gt;</description>
      </item>
      <item>
          <title>Programming an Arduino the Hard Way</title>
          <pubDate>Wed, 10 May 2023 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/programming-an-arduino-the-hard-way/</link>
          <guid>https://www.gridbugs.org/programming-an-arduino-the-hard-way/</guid>
          <description xml:base="https://www.gridbugs.org/programming-an-arduino-the-hard-way/">&lt;p&gt;This is a guide I wrote mostly for my future self on how to set up an ergonomic
development environment for writing Arduino programs in c without any
Arduino-specific tools and using an Arduino to make a simple circuit with some
flashing LEDs. I’ll also discus options for powering the Arduino from a 12v
DC power supply.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;programming-an-arduino-the-hard-way&#x2F;arduino1.jpg&quot; alt=&quot;A breadboard holding an Arduino and several other components including a range of coloured LEDs, some of which are on.&quot; &#x2F;&gt;&lt;&#x2F;p&gt;</description>
      </item>
      <item>
          <title>Chargrid SDL Frontend</title>
          <pubDate>Thu, 16 Mar 2023 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/chargrid-sdl-frontend/</link>
          <guid>https://www.gridbugs.org/chargrid-sdl-frontend/</guid>
          <description xml:base="https://www.gridbugs.org/chargrid-sdl-frontend/">&lt;p&gt;One problem with using the rust programming language for game development is the
notoriously slow compile times. For my recent 7DRL project
&lt;a href=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;boat-journey&#x2F;&quot;&gt;Boat Journey&lt;&#x2F;a&gt; an incremental debug build takes about 6
seconds on my main development machine. This is frustratingly slow when making
many minor gameplay tweaks during playtesting.&lt;&#x2F;p&gt;</description>
      </item>
      <item>
          <title>7 Day Roguelike 2023: Mechanics, Content, Playtesting</title>
          <pubDate>Sun, 12 Mar 2023 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/7drl2023-day7/</link>
          <guid>https://www.gridbugs.org/7drl2023-day7/</guid>
          <description xml:base="https://www.gridbugs.org/7drl2023-day7/">&lt;p&gt;The sun is peeking beneath my curtains and another 7DRL draws to a
close. Today I implemented the passenger mechanic, the trading mechanic,
collectable junk (the game’s currency), the combat system via abilities gained
from passengers, and added 7 different passengers you can pick up, each granting
you a unique ability.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;7drl2023-day7&#x2F;screenshot3.png&quot; alt=&quot;Screenshot of the finished game&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;The game is playable and downloadable on itch.io at
&lt;a href=&quot;https:&#x2F;&#x2F;gridbugs.itch.io&#x2F;boat-journey&quot;&gt;gridbugs.itch.io&#x2F;boat-journey&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;During playtesting I found some interesting emergent mechanics.&lt;&#x2F;p&gt;</description>
      </item>
      <item>
          <title>Boat Journey</title>
          <pubDate>Sun, 12 Mar 2023 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/boat-journey/</link>
          <guid>https://www.gridbugs.org/boat-journey/</guid>
          <description xml:base="https://www.gridbugs.org/boat-journey/">&lt;p&gt;Boat Journey is a turn-based game where you drive a boat through a
procedurally-generated landscape on a voyage along a river destined for the
ocean. Accept passengers to have them help you on your journey. Fight monsters,
collect junk, trade the junk for fuel, use the fuel to travel to the ocean.&lt;&#x2F;p&gt;
&lt;p&gt;You can play it in a web browser or download the game from &lt;a href=&quot;https:&#x2F;&#x2F;gridbugs.itch.io&#x2F;boat-journey&quot;&gt;gridbugs.itch.io&#x2F;boat-journey&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;The source code for the game is at
&lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;gridbugs&#x2F;boat-journey&quot;&gt;github.com&#x2F;gridbugs&#x2F;boat-journey&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;boat-journey&#x2F;cover.png&quot; alt=&quot;Text-based drawing of a boat at the end of a pier&quot; &#x2F;&gt;&lt;&#x2F;p&gt;</description>
      </item>
      <item>
          <title>7 Day Roguelike 2023: Passengers, Art</title>
          <pubDate>Sat, 11 Mar 2023 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/7drl2023-day6/</link>
          <guid>https://www.gridbugs.org/7drl2023-day6/</guid>
          <description xml:base="https://www.gridbugs.org/7drl2023-day6/">&lt;p&gt;Today I drew a bunch of background images for menus and the victory screen.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;7drl2023-day6&#x2F;title.png&quot; alt=&quot;Title screen showing main menu and a picture of a boat&quot; &#x2F;&gt;&lt;&#x2F;p&gt;</description>
      </item>
      <item>
          <title>7 Day Roguelike 2023: UI, Night, Loss, Menus</title>
          <pubDate>Fri, 10 Mar 2023 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/7drl2023-day5/</link>
          <guid>https://www.gridbugs.org/7drl2023-day5/</guid>
          <description xml:base="https://www.gridbugs.org/7drl2023-day5/">&lt;p&gt;I added stats like health, fuel, and daylight and display them in the game’s UI.
There’s a hinting system that displays hints based on what tiles are currently
on the screen.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;7drl2023-day5&#x2F;screenshot.png&quot; alt=&quot;Screenshot showing the game’s UI&quot; &#x2F;&gt;&lt;&#x2F;p&gt;</description>
      </item>
      <item>
          <title>7 Day Roguelike 2023: Even More Procedural Generation!</title>
          <pubDate>Thu, 09 Mar 2023 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/7drl2023-day4/</link>
          <guid>https://www.gridbugs.org/7drl2023-day4/</guid>
          <description xml:base="https://www.gridbugs.org/7drl2023-day4/">&lt;p&gt;Yet another day of working on procedural generation and I think I’m finally finished (the
procgen - not the game!). I added a generator for the starting town, a swamp
area, a flooded city area, and dungeons which are accessible from the city.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;7drl2023-day4&#x2F;town.png&quot; alt=&quot;Screenshot of the starting town&quot; &#x2F;&gt;&lt;&#x2F;p&gt;</description>
      </item>
      <item>
          <title>7 Day Roguelike 2023: More Procedural Generation</title>
          <pubDate>Wed, 08 Mar 2023 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/7drl2023-day3/</link>
          <guid>https://www.gridbugs.org/7drl2023-day3/</guid>
          <description xml:base="https://www.gridbugs.org/7drl2023-day3/">&lt;p&gt;Another day of mostly working on procedural generation. This game is by far the
most complex procgen project I’ve done. I’ve integrated the river generator into
the game engine and added a couple of pools along the river which I’ll populate
with settlements. There is also a small lake at the beginning of the river and
an ocean where the game ends.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;7drl2023-day3&#x2F;screenshot.png&quot; alt=&quot;Screenshot showing the boat in a river lined with rocks and trees&quot; &#x2F;&gt;&lt;&#x2F;p&gt;</description>
      </item>
      <item>
          <title>7 Day Roguelike 2023: Procedural Generation</title>
          <pubDate>Tue, 07 Mar 2023 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/7drl2023-day2/</link>
          <guid>https://www.gridbugs.org/7drl2023-day2/</guid>
          <description xml:base="https://www.gridbugs.org/7drl2023-day2/">&lt;p&gt;I spent this evening making a procedural generator for the shape of river and
choosing points along the river to place settlements. While I iterate on
procedural generation I’m working in a separate project to the main game so I
don’t need to deal with the complexities of integrating the generated levels
into the game while I’m also figuring out how level generation will work at all.&lt;&#x2F;p&gt;
&lt;p&gt;Here’s the debug output of the terrain generator so far showing the shape of the
river and the points where settlements will go.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;7drl2023-day2&#x2F;river.png&quot; alt=&quot;Debug image showing a procedurally-generated river&quot; &#x2F;&gt;&lt;&#x2F;p&gt;</description>
      </item>
      <item>
          <title>7 Day Roguelike 2023: Bootstrapping, Boat Gameplay, Vision</title>
          <pubDate>Mon, 06 Mar 2023 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/7drl2023-day1/</link>
          <guid>https://www.gridbugs.org/7drl2023-day1/</guid>
          <description xml:base="https://www.gridbugs.org/7drl2023-day1/">&lt;p&gt;It’s the end of my first day of hacking on this year’s 7DRL.
My game will be called “Boat Journey”. It’s about driving a boat along a river
with the goal of reaching the ocean without running out of fuel or otherwise
becoming stranded, while picking up passengers who give you additional
abilities.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;7drl2023-day1&#x2F;screenshot.png&quot; alt=&quot;Screenshot of gameplay showing the player standing on a boat adjacent to a small island&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;I’m intentionally going for a very minimal graphical style this year since the
past few years I’ve found I spend more time on fancy graphics than I would like,
and less time working on gameplay features.&lt;&#x2F;p&gt;</description>
      </item>
      <item>
          <title>Playing sound on the NES by directly setting its DMC output</title>
          <pubDate>Sat, 21 Jan 2023 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/playing-sound-on-the-nes-by-directly-setting-its-dmc-output/</link>
          <guid>https://www.gridbugs.org/playing-sound-on-the-nes-by-directly-setting-its-dmc-output/</guid>
          <description xml:base="https://www.gridbugs.org/playing-sound-on-the-nes-by-directly-setting-its-dmc-output/">&lt;p&gt;Games on the Nintendo Entertainment System play audio using a handful of
tools within the device’s Audio Processing Unit (APU). The Delta Modulation
Channel (DMC) is the most expressive such tool as it can play arbitrary audio
data. Any sound you’ve heard come out of a NES other than variations on
square and triangle waves, and noise, was played using the DMC.&lt;&#x2F;p&gt;
&lt;p&gt;As its name suggests, the DMC can play &lt;a href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;Delta_modulation&quot;&gt;delta
modulated&lt;&#x2F;a&gt;
audio data, where a signal is represented by a sequence of relative changes
rather than a sequence of samples. The DMC also exposes a register that allows
audio samples to be written directly to its output, and that’s what this post
will be about.&lt;&#x2F;p&gt;
&lt;p&gt;This post is aimed at people looking to write programs to play
audio on the NES, or write an emulator for the NES’s APU. I found &lt;a href=&quot;https:&#x2F;&#x2F;www.nesdev.org&#x2F;wiki&#x2F;APU_DMC&quot;&gt;the DMC
documentation on the NesDev wiki&lt;&#x2F;a&gt; great for
low-level technical details but after reading it I felt I had little
intuition for what to expect when actually using the DMC, so I did some experiments which
I will share.&lt;&#x2F;p&gt;</description>
      </item>
      <item>
          <title>Demystifying Floating Points</title>
          <pubDate>Sun, 15 Jan 2023 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/demystifying-floating-points/</link>
          <guid>https://www.gridbugs.org/demystifying-floating-points/</guid>
          <description xml:base="https://www.gridbugs.org/demystifying-floating-points/">&lt;p&gt;Floating points are a way of representing numbers based on the idea that the
larger the magnitude of a number, the less we care about knowing its precise
value. If you’re anything like me (until recently) you use floating points regularly in your code
with a rough understanding of what to expect, but don’t understand the specifics
of what floats can and can’t represent. For example what’s the biggest floating
point, or the smallest positive floating point? Or how many times can you add
1.0 to a floating point before &lt;em&gt;something bad&lt;&#x2F;em&gt; happens, and what is that
&lt;em&gt;something bad&lt;&#x2F;em&gt;? How many floating point values are there between 0 and 1? What
about between 1 and 2?&lt;&#x2F;p&gt;
&lt;p&gt;The answer to these questions is, of course, “it depends”, so let’s talk about a
specific floating point standard, the “binary32” type defined in IEEE 754-2008.
This is the commonly-found single-precision floating point type, which
backs the &lt;code&gt;f32&lt;&#x2F;code&gt; type in rust, and usually backs the &lt;code&gt;float&lt;&#x2F;code&gt; type in c
(though of course technically this is left unspecified).
From now on this this post, I will refer to this type as simply “float”.&lt;&#x2F;p&gt;
&lt;p&gt;Here’s what a float can represent:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;all powers of 2 from 2^-126 to 2^127&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;for each consecutive pair of powers of 2, there are 8,388,607 (that’s 2^23 - 1)
additional numbers, evenly spaced apart&lt;&#x2F;strong&gt;&lt;&#x2F;li&gt;
&lt;li&gt;zero&lt;&#x2F;li&gt;
&lt;li&gt;infinity&lt;&#x2F;li&gt;
&lt;li&gt;negative versions of all of the above&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;The second point is the most important for understanding floating points. &lt;strong&gt;Each
successive pair of powers of 2 has 2^23 - 1 floating point values evenly spread
out between them.&lt;&#x2F;strong&gt;
There are 2^23 - 1 floats between 0.125 and 0.25, between 1 and 2, between 1024
and 2048, and between 8,388,608 (2^23) and 16,777,216 (2^24). As the numeric
range between consecutive powers of 2 increases, the number of floats between
them stays the same at 2^23 - 1; the floats just get more spread out.
This is the reason that values with lower
magnitudes can be more precisely represented with floating points.&lt;&#x2F;p&gt;
&lt;p&gt;Some implications of this:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;in between 1 and 2, consecutive float values are 2^-23 apart from one
another&lt;&#x2F;li&gt;
&lt;li&gt;in between 2 and 4, consecutive float values are 2^-22 apart from another&lt;&#x2F;li&gt;
&lt;li&gt;in between 8,388,608 (2^23) and 16,777,216 (2^24), consecutive float values
are 1 apart from one another&lt;&#x2F;li&gt;
&lt;li&gt;for each power of 2, there are 8,388,608 (2^23) floats (1 for the power of 2,
and 2^23 - 1 between (exclusive) the power of 2 and the next power of 2)&lt;&#x2F;li&gt;
&lt;li&gt;the number of positive floats less than 1 is 126 x 2^23 = 1,056,964,608
&lt;ul&gt;
&lt;li&gt;there are 126 powers of 2 less than 1&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;the number of positive floats greater than or equal to 1 is 128 x 2^23 = 1,073,741,824
&lt;ul&gt;
&lt;li&gt;there are 128 powers of 2 greater than or equal to 1 (including 2^0 = 1)&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;all floats above 8,388,608 (2^23) are integers&lt;&#x2F;li&gt;
&lt;li&gt;all floats above 16,777,216 (2^24) are even&lt;&#x2F;li&gt;
&lt;li&gt;if you attempt to add 1 to 16,777,216 (2^24), the result will be 16,777,216 (2^24)&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Here’s how floats are encoded:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;demystifying-floating-points&#x2F;bits.svg&quot; alt=&quot;diagram: [ Sign (1 bit) | Exponent (8 bits) | Fraction (23 bits) ]&quot; &#x2F;&gt;&lt;&#x2F;p&gt;</description>
      </item>
      <item>
          <title>If you use a custom linker script, _start might not be your entry point</title>
          <pubDate>Sat, 07 Jan 2023 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/if-you-use-a-custom-linker-script-_start-is-not-necessarily-the-entry-point/</link>
          <guid>https://www.gridbugs.org/if-you-use-a-custom-linker-script-_start-is-not-necessarily-the-entry-point/</guid>
          <description xml:base="https://www.gridbugs.org/if-you-use-a-custom-linker-script-_start-is-not-necessarily-the-entry-point/">&lt;p&gt;For most of my life I took for granted that programs begin executing at an
address denoted by the symbol &lt;code&gt;_start&lt;&#x2F;code&gt; (a single underscore, followed by the
word “start”). Turns out it’s not so simple.&lt;&#x2F;p&gt;
&lt;p&gt;Take this x86 assembly program that prints “Hello, World!” on Linux:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;asm&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-asm &quot;&gt;&lt;code class=&quot;language-asm&quot; data-lang=&quot;asm&quot;&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;global &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;_start
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;.text
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;message:
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;    .ascii &lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;Hello, World!\n&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;_start:
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;    # print the message
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;mov &lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;$&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;%&lt;&#x2F;span&gt;&lt;span&gt;rax        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;# &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;syscall &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;1 &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;is write
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;mov &lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;$&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;%&lt;&#x2F;span&gt;&lt;span&gt;rdi        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;# file descriptor &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;1 &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;is stdout
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;mov &lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;$&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;message&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;%&lt;&#x2F;span&gt;&lt;span&gt;rsi  &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;# pass address of messsage
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;mov &lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;$&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;13&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;%&lt;&#x2F;span&gt;&lt;span&gt;rdx       &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;# pass length of message
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;syscall             &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;# perform write system &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;call
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;    # exit
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;mov &lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;$&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;60&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;%&lt;&#x2F;span&gt;&lt;span&gt;rax       &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;# &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;syscall &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;60 &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;is exit
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;mov &lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;$&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;%&lt;&#x2F;span&gt;&lt;span&gt;rdi        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;# pass exit status of &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;0
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;syscall             &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;# perform exit system &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;call
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;To compile and run this program (assume it’s in the file hello.s):&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;$ gcc -c hello.s   # compile hello.s to object file hello.o
&lt;&#x2F;span&gt;&lt;span&gt;$ ld hello.o       # link hello.o to executable a.out
&lt;&#x2F;span&gt;&lt;span&gt;$ .&#x2F;a.out
&lt;&#x2F;span&gt;&lt;span&gt;Hello, World!
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;</description>
      </item>
      <item>
          <title>Rain Forest</title>
          <pubDate>Wed, 06 Apr 2022 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/rain-forest/</link>
          <guid>https://www.gridbugs.org/rain-forest/</guid>
          <description xml:base="https://www.gridbugs.org/rain-forest/">&lt;p&gt;Rain Forest is a roguelike game about spending a few days in a forest, in the
rain. Figure out what daily tasks you can perform to stay motivated to remain
in the forest, and try not to get too wet from the constant rain and rising
flood water. And above all, try to have a relaxing time.&lt;&#x2F;p&gt;
&lt;p&gt;This game is my 7th entry in the &lt;a href=&quot;https:&#x2F;&#x2F;itch.io&#x2F;jam&#x2F;7drl-challenge-2022&quot;&gt;7 Day Roguelike&lt;&#x2F;a&gt; game jam.&lt;&#x2F;p&gt;
&lt;p&gt;Play or download Rain Forest on &lt;a href=&quot;https:&#x2F;&#x2F;gridbugs.itch.io&#x2F;rain-forest&quot;&gt;its itch.io page&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;rain-forest&#x2F;screenshot.png&quot; alt=&quot;screenshot.png&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;The game is open source, and the code is available on &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;gridbugs&#x2F;rainforest&quot;&gt;github&lt;&#x2F;a&gt;. It’s written in
rust, and the rendering, IO handling, UI and cross-platform support is handled
by my &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;gridbugs&#x2F;chargrid&quot;&gt;chargrid&lt;&#x2F;a&gt; library.&lt;&#x2F;p&gt;
&lt;p&gt;My main focus this year was on setting the mood of a rainy forest. I personally
find rain very calming, and tried to convey this calmness in the aesthetic of
Rain Forest. I’m quite satisfied with how the game turned out visually. I’m
planning to release a new version at some point that includes rain sounds (not
for consideration in the 7DRL of course) to complete the mood.&lt;&#x2F;p&gt;
&lt;p&gt;The gameplay is not particularly exciting; there’s no combat, and most of the
game is wandering around locating the various sites in the forest you can use
to gain motivation, and coming up with a daily routine that evolves as the
flood water rises and the rain gets heavier.&lt;&#x2F;p&gt;
&lt;p&gt;I had some ideas to make it more interactive, like digging ditches to redirect
the water, or stepping stones which can be moved into water to allow the player
to cross safely. These all made it into the game but there’s little incentive
for the player to actually use these features. I couldn’t work out a way to
make it necessary to engage in these mechanics while also being fun. I’m
not sure yet whether I’ll try to come up with gameplay changes to try to
make the game more fun. A part of me wants to strip away the features which
the game doesn’t need and just keep the minimal experience of walking
around in the forest in the rain.&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>7 Day Roguelike 2022: Complete</title>
          <pubDate>Fri, 11 Mar 2022 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/7drl2022-day7/</link>
          <guid>https://www.gridbugs.org/7drl2022-day7/</guid>
          <description xml:base="https://www.gridbugs.org/7drl2022-day7/">&lt;p&gt;I added a win condition, implemented an equipment system, and did a bunch of play testing and balancing.
The game is finished and submitted to the &lt;a href=&quot;https:&#x2F;&#x2F;itch.io&#x2F;jam&#x2F;7drl-challenge-2022&quot;&gt;7drl&lt;&#x2F;a&gt; jam.
Play it in a browser, or download binaries from &lt;a href=&quot;https:&#x2F;&#x2F;gridbugs.itch.io&#x2F;rain-forest&quot;&gt;it’s itch.io page&lt;&#x2F;a&gt;.
The source code is on &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;gridbugs&#x2F;rainforest&quot;&gt;github&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;7drl2022-day7&#x2F;2.png&quot; alt=&quot;2.png&quot; &#x2F;&gt;&lt;&#x2F;p&gt;</description>
      </item>
      <item>
          <title>7 Day Roguelike 2022: Interactions</title>
          <pubDate>Thu, 10 Mar 2022 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/7drl2022-day6/</link>
          <guid>https://www.gridbugs.org/7drl2022-day6/</guid>
          <description xml:base="https://www.gridbugs.org/7drl2022-day6/">&lt;p&gt;I implemented most of the gameplay tonight. Gameplay consists of performing simple daily tasks
like making tea or placing a flower on an altar.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;7drl2022-day6&#x2F;1.png&quot; alt=&quot;1.png&quot; &#x2F;&gt;&lt;&#x2F;p&gt;</description>
      </item>
      <item>
          <title>7 Day Roguelike 2022: Motivation</title>
          <pubDate>Wed, 09 Mar 2022 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/7drl2022-day5/</link>
          <guid>https://www.gridbugs.org/7drl2022-day5/</guid>
          <description xml:base="https://www.gridbugs.org/7drl2022-day5/">&lt;p&gt;Tonight I added the motivation system.
It’s a number which gradually ticks down, and if it gets to zero, you lose.
It ticks down faster when you’re uncomfortable, such as when you’re standing
in the rain or flood water.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;7drl2022-day5&#x2F;lake.png&quot; alt=&quot;lake.png&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Various things can restore your motivation, like sleeping, or visiting the lake.&lt;&#x2F;p&gt;</description>
      </item>
      <item>
          <title>7 Day Roguelike 2022: Time</title>
          <pubDate>Tue, 08 Mar 2022 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/7drl2022-day4/</link>
          <guid>https://www.gridbugs.org/7drl2022-day4/</guid>
          <description xml:base="https://www.gridbugs.org/7drl2022-day4/">&lt;p&gt;Tonight I added the concept of time to the game.
Each day, as time progresses, the light and visibility systems respond to the passage of time,
simulating a sunrise and sunset, and making it dark at night.
There are three different rain modes (light, medium, heavy) and a randomized (ahem, “procedural”)
schedule that makes the rain get heavier over time (on average).&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;7drl2022-day4&#x2F;flood.png&quot; alt=&quot;flood.png&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Also each day the flood level rises, and when you walk into the flood water the ‘@’ sign is partially submerged!&lt;&#x2F;p&gt;</description>
      </item>
      <item>
          <title>7 Day Roguelike 2022: Content</title>
          <pubDate>Mon, 07 Mar 2022 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/7drl2022-day3/</link>
          <guid>https://www.gridbugs.org/7drl2022-day3/</guid>
          <description xml:base="https://www.gridbugs.org/7drl2022-day3/">&lt;p&gt;I spent the day adding assets. There’s a large lake one one side of the map with a pier.
Lamp posts, grass, and rocks are randomly placed around the forest.
There are patches of flowers and tea plants.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;7drl2022-day3&#x2F;1.png&quot; alt=&quot;1.png&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;In addition to looking nice, the grass serves the purpose of showing players where they
have been, as when you walk on the grass it gets trampled and its sprite changes.&lt;&#x2F;p&gt;</description>
      </item>
      <item>
          <title>7 Day Roguelike 2022: Atmosphere</title>
          <pubDate>Sun, 06 Mar 2022 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/7drl2022-day2/</link>
          <guid>https://www.gridbugs.org/7drl2022-day2/</guid>
          <description xml:base="https://www.gridbugs.org/7drl2022-day2/">&lt;p&gt;The aspect I care most about this year is setting the mood of being inside
(or outside) on a rainy day in the woods. I spent most of today building
the rain and mist systems.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;7drl2022-day2&#x2F;screenshot.png&quot; alt=&quot;screenshot.png&quot; &#x2F;&gt;&lt;&#x2F;p&gt;</description>
      </item>
      <item>
          <title>7 Day Roguelike 2022: Bootstrapping</title>
          <pubDate>Sat, 05 Mar 2022 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/7drl2022-day1/</link>
          <guid>https://www.gridbugs.org/7drl2022-day1/</guid>
          <description xml:base="https://www.gridbugs.org/7drl2022-day1/">&lt;p&gt;I spent most of the day bootstrapping the project.
Unlike the last few years, this year I’ve elected to start from scratch
rather than from an existing project, though I am copying code aggressively
from last years entry. The reason for doing this is to prevent needing to
prevent the technical debt from accumulating over multiple game jams hacking
on the same code base.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;7drl2022-day1&#x2F;screenshot.png&quot; alt=&quot;screenshot.png&quot; &#x2F;&gt;&lt;&#x2F;p&gt;</description>
      </item>
      <item>
          <title>Reverse-Engineering NES Tetris to add Hard Drop</title>
          <pubDate>Sun, 21 Mar 2021 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/reverse-engineering-nes-tetris-to-add-hard-drop/</link>
          <guid>https://www.gridbugs.org/reverse-engineering-nes-tetris-to-add-hard-drop/</guid>
          <description xml:base="https://www.gridbugs.org/reverse-engineering-nes-tetris-to-add-hard-drop/">&lt;style&gt;
.nes-screenshot img {
    width: 512px;
    height: 480px;
    image-rendering: crisp-edges;
    image-rendering: pixelated;
}
.nes-screenshot-half img {
    width: 256px;
    height: 240px;
    image-rendering: crisp-edges;
    image-rendering: pixelated;
}
.nes-screenshot-quarter img {
    width: 128px;
    height: 120px;
    image-rendering: crisp-edges;
    image-rendering: pixelated;
}
&lt;&#x2F;style&gt;
&lt;p&gt;&lt;a href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;Tetris_(NES_video_game)&quot;&gt;Tetris for the NES&lt;&#x2F;a&gt; is one of my favourite versions of Tetris.
My only complaint is that it lacks the ability to “hard drop” - instantly dropping the current piece and locking it into place.&lt;&#x2F;p&gt;
&lt;p&gt;Let’s change that!&lt;&#x2F;p&gt;
&lt;p&gt;This post describes a modification I made to NES Tetris so that pressing the “up” button causes the current piece to hard drop,
and so that the game renders a “ghost piece” - a dotted outline of the current piece showing where it will land.&lt;&#x2F;p&gt;
&lt;div class=&quot;nes-screenshot&quot;&gt;
&lt;img src=&quot;screenshot1.png&quot;&gt;
&lt;&#x2F;div&gt;</description>
      </item>
      <item>
          <title>Orbital Decay</title>
          <pubDate>Sun, 14 Mar 2021 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/orbital-decay/</link>
          <guid>https://www.gridbugs.org/orbital-decay/</guid>
          <description xml:base="https://www.gridbugs.org/orbital-decay/">&lt;p&gt;Orbital Decay is a traditional roguelike where you fight zombies on a space station.
It features destructible terrain, an air pressure system, and ranged combat.
Try not to shoot the hull, or you may find yourself sucked out into space!&lt;&#x2F;p&gt;
&lt;p&gt;In keeping with the traditions of the genre, graphics are made entirely of text,
though each tile is made of 3x3 characters.
There is a soundtrack of 6 songs, and about 10 different sound effects.
It’s playable in a browser, or a standalone graphical program. MacOS and Linux
users can also play the game in a terminal.&lt;&#x2F;p&gt;
&lt;p&gt;This is my entry in the 7 Day Roguelike 2021 game jam.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Play or download Orbital Decay on &lt;a href=&quot;https:&#x2F;&#x2F;gridbugs.itch.io&#x2F;orbital-decay&quot;&gt;its itch.io page&lt;&#x2F;a&gt;.&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;orbital-decay&#x2F;screenshot1.png&quot; alt=&quot;screenshot1.png&quot; &#x2F;&gt;&lt;&#x2F;p&gt;</description>
      </item>
      <item>
          <title>7 Day Roguelike 2021: Gamepad Support, Audio, Maps, Help Screen, Play Testing</title>
          <pubDate>Fri, 12 Mar 2021 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/7drl2021-day7/</link>
          <guid>https://www.gridbugs.org/7drl2021-day7/</guid>
          <description xml:base="https://www.gridbugs.org/7drl2021-day7/">&lt;p&gt;Firstly, here’s a screenshot of my first organic encounter with the final level of Orbital Decay:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;7drl2021-day7&#x2F;screenshot5.png&quot; alt=&quot;screenshot5.png&quot; &#x2F;&gt;&lt;&#x2F;p&gt;</description>
      </item>
      <item>
          <title>7 Day Roguelike 2021: Enemies, Message Log, Space Gradient</title>
          <pubDate>Thu, 11 Mar 2021 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/7drl2021-day6/</link>
          <guid>https://www.gridbugs.org/7drl2021-day6/</guid>
          <description xml:base="https://www.gridbugs.org/7drl2021-day6/">&lt;p&gt;Tonight I added a range of enemies, a message log, and made the space background a gradient.
The game is now feature-complete! I also added placeholder sound effects so when I get my hands on
real sound effects I can just slot them in.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;7drl2021-day6&#x2F;screenshot.png&quot; alt=&quot;screenshot.png&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Tomorrow I’ll be adding music and sound effects, play-testing, polishing, and fixing bugs.&lt;&#x2F;p&gt;</description>
      </item>
      <item>
          <title>7 Day Roguelike 2021: Weapons</title>
          <pubDate>Wed, 10 Mar 2021 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/7drl2021-day5/</link>
          <guid>https://www.gridbugs.org/7drl2021-day5/</guid>
          <description xml:base="https://www.gridbugs.org/7drl2021-day5/">&lt;p&gt;Tonight I added weapons.
This screenshot shows the game’s arsenal.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;7drl2021-day5&#x2F;weapons.png&quot; alt=&quot;weapons.png&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Other than art, most of the work was setting up interactions like displaying a prompt before picking up a new weapon
that would replace the weapon in its destination weapon slot.&lt;&#x2F;p&gt;</description>
      </item>
      <item>
          <title>7 Day Roguelike 2021: Upgrades</title>
          <pubDate>Tue, 09 Mar 2021 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/7drl2021-day4/</link>
          <guid>https://www.gridbugs.org/7drl2021-day4/</guid>
          <description xml:base="https://www.gridbugs.org/7drl2021-day4/">&lt;p&gt;Today I implemented the upgrade system.
You collect credits which can spent at an upgrade store.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;7drl2021-day4&#x2F;screenshot.png&quot; alt=&quot;screenshot.png&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;The store presents a menu from which one of three upgrades can be chosen.
There are three upgrade tracks with two levels each. Each level unlocks a new passive character upgrade.
The menu includes a description of the currently-selected upgrade.&lt;&#x2F;p&gt;</description>
      </item>
      <item>
          <title>7 Day Roguelike 2021: Depressurisation</title>
          <pubDate>Mon, 08 Mar 2021 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/7drl2021-day3/</link>
          <guid>https://www.gridbugs.org/7drl2021-day3/</guid>
          <description xml:base="https://www.gridbugs.org/7drl2021-day3/">&lt;p&gt;I spent the day getting depressurisation  working.
When the hull gets pierced, the area of the station exposed to space is depressurised.
Any characters in that part of the station are pulled towards the breach for several turns
until the pressure is equalised.&lt;&#x2F;p&gt;
&lt;p&gt;The red warning light indicates that an area is being depressurised.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;7drl2021-day3&#x2F;1.png&quot; alt=&quot;1.png&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Once the pressure equalises, a blue warning light indicates that an area has no air.
The oxygen meter will start draining, and when it runs out the health meter will start draining.&lt;&#x2F;p&gt;</description>
      </item>
      <item>
          <title>7 Day Roguelike 2021: NPCs, Pathfinding, First Level, Final Level, Projectiles, Destructible Terrain</title>
          <pubDate>Sun, 07 Mar 2021 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/7drl2021-day2/</link>
          <guid>https://www.gridbugs.org/7drl2021-day2/</guid>
          <description xml:base="https://www.gridbugs.org/7drl2021-day2/">&lt;p&gt;I got through a good amount of core gameplay mechanics today including NPCs, projectiles and destructible terrain.
I also spent some time focussing on flair, such as special first and last levels, animations in the game menu,
death screens, and smoke trails on bullets.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;7drl2021-day2&#x2F;intro.png&quot; alt=&quot;intro.png&quot; &#x2F;&gt;&lt;&#x2F;p&gt;</description>
      </item>
      <item>
          <title>7 Day Roguelike 2021: Procedural Generation, Rendering, Pretty Menus, End Text</title>
          <pubDate>Sat, 06 Mar 2021 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/7drl2021-day1/</link>
          <guid>https://www.gridbugs.org/7drl2021-day1/</guid>
          <description xml:base="https://www.gridbugs.org/7drl2021-day1/">&lt;style&gt;
.orbital-decay {
    color: #D08C15;
    background-color: #00003B;
}
&lt;&#x2F;style&gt;
&lt;h2 id=&quot;orbital-decay&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#orbital-decay&quot; aria-label=&quot;Anchor link for: orbital-decay&quot;&gt;&lt;span class=&quot;orbital-decay&quot;&gt;Orbital Decay&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;7drl2021-day1&#x2F;screenshot.png&quot; alt=&quot;screenshot.png&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p class=&quot;orbital-decay&quot;&gt;
You tape over the flashing warning light.
An overheating engine is the least of your worries.
Gotta focus.
&lt;&#x2F;p&gt;
&lt;p class=&quot;orbital-decay&quot;&gt;
The space station looms ahead.
It&#x27;s out of fuel, and about to come crashing down to Earth.
Unless you get to it first.
Special delivery: 1 hydrogen fuel cell with enough juice to kick the station out of this pesky atmosphere and back into space where it belongs.
&lt;&#x2F;p&gt;</description>
      </item>
      <item>
          <title>7 Day Roguelike 2021: Preparation</title>
          <pubDate>Thu, 04 Mar 2021 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/7drl2021-prep/</link>
          <guid>https://www.gridbugs.org/7drl2021-prep/</guid>
          <description xml:base="https://www.gridbugs.org/7drl2021-prep/">&lt;style&gt;
.orbital-decay {
    color: #D08C15;
    background-color: #00003B;
}
&lt;&#x2F;style&gt;
&lt;p&gt;This year’s 7DRL is days away.
Over the last few weeks I’ve dusted off my game engine and come up with a plan for the jam.
I’m going to revisit the idea behind my first 7DRL - &lt;a href=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;skeleton-crew&#x2F;&quot;&gt;Skeleton Crew&lt;&#x2F;a&gt; -
which was a game about fighting zombies in a space ship where shooting the hull caused the
ship to decompress.&lt;&#x2F;p&gt;</description>
      </item>
      <item>
          <title>2020 Recap</title>
          <pubDate>Fri, 25 Dec 2020 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/2020-recap/</link>
          <guid>https://www.gridbugs.org/2020-recap/</guid>
          <description xml:base="https://www.gridbugs.org/2020-recap/">&lt;p&gt;This is a look at some of the things I’ve worked on in my spare time
during 2020. I started branching into new areas this year such as
hobby OS dev, writing, and music theory, as well as continuing to work
on roguelike games and supporting libraries.&lt;&#x2F;p&gt;</description>
      </item>
      <item>
          <title>NTFS on FreeBSD</title>
          <pubDate>Fri, 18 Dec 2020 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/daily/ntfs-on-freebsd/</link>
          <guid>https://www.gridbugs.org/daily/ntfs-on-freebsd/</guid>
          <description xml:base="https://www.gridbugs.org/daily/ntfs-on-freebsd/">&lt;p&gt;Here’s a guide for myself on how to format and mount an NTFS partition on FreeBSD.
These examples were run on FreeBSD 12.2-RELEASE.&lt;&#x2F;p&gt;
&lt;p&gt;List disks:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;# geom disk list
&lt;&#x2F;span&gt;&lt;span&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;Geom name: da1
&lt;&#x2F;span&gt;&lt;span&gt;Providers:
&lt;&#x2F;span&gt;&lt;span&gt;1. Name: da1
&lt;&#x2F;span&gt;&lt;span&gt;   Mediasize: 1000204886016 (932G)
&lt;&#x2F;span&gt;&lt;span&gt;   Sectorsize: 512
&lt;&#x2F;span&gt;&lt;span&gt;   Stripesize: 4096
&lt;&#x2F;span&gt;&lt;span&gt;   Stripeoffset: 0
&lt;&#x2F;span&gt;&lt;span&gt;   Mode: r0w0e0
&lt;&#x2F;span&gt;&lt;span&gt;   descr: HGST TOURO S
&lt;&#x2F;span&gt;&lt;span&gt;   lunid: 5000000000000001
&lt;&#x2F;span&gt;&lt;span&gt;   ident: 21001410170002100173
&lt;&#x2F;span&gt;&lt;span&gt;   rotationrate: unknown
&lt;&#x2F;span&gt;&lt;span&gt;   fwsectors: 63
&lt;&#x2F;span&gt;&lt;span&gt;   fwheads: 255
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This guide will create a single giant NTFS partition taking up the entirety of da1.&lt;&#x2F;p&gt;
&lt;p&gt;Destroy the existing partition table if necessary:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;# gpart destroy -F da1
&lt;&#x2F;span&gt;&lt;span&gt;da1 destroyed
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Create the (GPT) partition table if you don’t already have one.&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;# gpart create -s GPT da1
&lt;&#x2F;span&gt;&lt;span&gt;da1 created
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Create a partition:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;# gpart add -t ms-basic-data da1
&lt;&#x2F;span&gt;&lt;span&gt;da1p1 added
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;A common mistake when trying to create an NTFS partition is to run:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;# gpart add -t ntfs da1
&lt;&#x2F;span&gt;&lt;span&gt;gpart: Invalid argument
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This is one of the least helpful error messages I can imagine.
&lt;code&gt;gpart&lt;&#x2F;code&gt; doesn’t let you create a partition with type “ntfs” on a disk with a GPT
partition table. You’d need to make a MBR partition table instead.&lt;&#x2F;p&gt;
&lt;p&gt;Format the partition:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;# NTFS_USE_UBLIO=0 mkntfs -vf &#x2F;dev&#x2F;da1p1
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The &lt;code&gt;mkntfs&lt;&#x2F;code&gt; command is part of the &lt;code&gt;sysutils&#x2F;fusefs-ntfs&lt;&#x2F;code&gt; package.
&lt;code&gt;-v&lt;&#x2F;code&gt; prints verbose output, &lt;code&gt;-f&lt;&#x2F;code&gt; skips zeroing out the partition and elides some checks to speed up
creating the partition. Setting &lt;code&gt;NTFS_USE_UBLIO&lt;&#x2F;code&gt; is a workaround for a bug in &lt;code&gt;mkntfs&lt;&#x2F;code&gt; which causes
the command to hang.&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;bugs.freebsd.org&#x2F;bugzilla&#x2F;show_bug.cgi?id=206978&quot;&gt;bug report&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;unix.stackexchange.com&#x2F;questions&#x2F;513732&#x2F;why-is-mkntfs-taking-such-a-long-time&quot;&gt;stack exchange post about the problem&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;chieflemming.wordpress.com&#x2F;tag&#x2F;freebsd&#x2F;&quot;&gt;blog post where I first read about this issue and the workaround&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Mount the partition:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;# ntfs-3g &#x2F;dev&#x2F;da1p1 &#x2F;mnt&#x2F;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The &lt;code&gt;ntfs-3g&lt;&#x2F;code&gt; program is also part of the &lt;code&gt;sysutils&#x2F;fusefs-ntfs&lt;&#x2F;code&gt; package.&lt;&#x2F;p&gt;
&lt;p&gt;Should this fail with the error &lt;code&gt;fuse: failed to open fuse device: No such file or directory&lt;&#x2F;code&gt;,
the &lt;code&gt;fuse&lt;&#x2F;code&gt; kernel module is likely not loaded. Load it with:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;# kldload fuse
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Set it to load on boot by adding the following to &#x2F;boot&#x2F;loader.conf:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;fuse_load=&amp;quot;YES&amp;quot;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;To unmount the partition:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;# umount &#x2F;mnt
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
</description>
      </item>
      <item>
          <title>NTFS on FreeBSD</title>
          <pubDate>Fri, 18 Dec 2020 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/ntfs-on-freebsd/</link>
          <guid>https://www.gridbugs.org/ntfs-on-freebsd/</guid>
          <description xml:base="https://www.gridbugs.org/ntfs-on-freebsd/">&lt;p&gt;Here’s a guide for myself on how to format and mount an NTFS partition on FreeBSD.
These examples were run on FreeBSD 12.2-RELEASE.&lt;&#x2F;p&gt;
&lt;p&gt;List disks:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;# geom disk list
&lt;&#x2F;span&gt;&lt;span&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;Geom name: da1
&lt;&#x2F;span&gt;&lt;span&gt;Providers:
&lt;&#x2F;span&gt;&lt;span&gt;1. Name: da1
&lt;&#x2F;span&gt;&lt;span&gt;   Mediasize: 1000204886016 (932G)
&lt;&#x2F;span&gt;&lt;span&gt;   Sectorsize: 512
&lt;&#x2F;span&gt;&lt;span&gt;   Stripesize: 4096
&lt;&#x2F;span&gt;&lt;span&gt;   Stripeoffset: 0
&lt;&#x2F;span&gt;&lt;span&gt;   Mode: r0w0e0
&lt;&#x2F;span&gt;&lt;span&gt;   descr: HGST TOURO S
&lt;&#x2F;span&gt;&lt;span&gt;   lunid: 5000000000000001
&lt;&#x2F;span&gt;&lt;span&gt;   ident: 21001410170002100173
&lt;&#x2F;span&gt;&lt;span&gt;   rotationrate: unknown
&lt;&#x2F;span&gt;&lt;span&gt;   fwsectors: 63
&lt;&#x2F;span&gt;&lt;span&gt;   fwheads: 255
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;</description>
      </item>
      <item>
          <title>dmenu_histogram</title>
          <pubDate>Sun, 29 Nov 2020 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/daily/dmenu-histogram/</link>
          <guid>https://www.gridbugs.org/daily/dmenu-histogram/</guid>
          <description xml:base="https://www.gridbugs.org/daily/dmenu-histogram/">&lt;p&gt;One of the programs I’ve made that most directly benefited my life is
&lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;gridbugs&#x2F;dotfiles&#x2F;blob&#x2F;master&#x2F;bin&#x2F;dmenu_histogram&quot;&gt;this little bash script in my dotfiles repo&lt;&#x2F;a&gt;
which runs the application launcher &lt;a href=&quot;https:&#x2F;&#x2F;tools.suckless.org&#x2F;dmenu&#x2F;&quot;&gt;dmenu&lt;&#x2F;a&gt; showing the most-frequently-used
programs first.&lt;&#x2F;p&gt;
&lt;p&gt;The typical way to run dmenu is to pipe the output of &lt;code&gt;dmenu_path&lt;&#x2F;code&gt; into &lt;code&gt;dmenu&lt;&#x2F;code&gt;.
Both commands come in the &lt;code&gt;dmenu&lt;&#x2F;code&gt; package. The former simply prints a list of all programs
in the user’s path:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;$ dmenu_path
&lt;&#x2F;span&gt;&lt;span&gt;[
&lt;&#x2F;span&gt;&lt;span&gt;2to3-2.7
&lt;&#x2F;span&gt;&lt;span&gt;2to3-3.7
&lt;&#x2F;span&gt;&lt;span&gt;aafire
&lt;&#x2F;span&gt;&lt;span&gt;aainfo
&lt;&#x2F;span&gt;&lt;span&gt;aalib-config
&lt;&#x2F;span&gt;&lt;span&gt;aasavefont
&lt;&#x2F;span&gt;&lt;span&gt;aatest
&lt;&#x2F;span&gt;&lt;span&gt;ac
&lt;&#x2F;span&gt;&lt;span&gt;accton
&lt;&#x2F;span&gt;&lt;span&gt;...
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The &lt;code&gt;dmenu&lt;&#x2F;code&gt; program reads a list of strings from its standard input and presents a graphical list.
The user selects a single item from the list, and dmenu prints the selection to its standard output.&lt;&#x2F;p&gt;
&lt;p&gt;For example:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;$ echo -e &amp;#39;hello\nworld&amp;#39;
&lt;&#x2F;span&gt;&lt;span&gt;hello
&lt;&#x2F;span&gt;&lt;span&gt;world
&lt;&#x2F;span&gt;&lt;span&gt;$ echo -e &amp;#39;hello\nworld&amp;#39; | dmenu
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The second command displays this GUI at the top of the screen:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;daily&#x2F;dmenu-histogram&#x2F;dmenu-hello-world.png&quot; alt=&quot;dmenu-hello-world.png&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Use the arrow keys to change the selection or start typing a name to narrow down the list:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;daily&#x2F;dmenu-histogram&#x2F;dmenu-world.png&quot; alt=&quot;dmenu-world.png&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Hit enter and dmenu exits after printing &lt;code&gt;world&lt;&#x2F;code&gt; to its standard output.&lt;&#x2F;p&gt;
&lt;p&gt;To use it as a program launcher, the dmenu package comes with a third program - a shell script
&lt;code&gt;dmenu_run&lt;&#x2F;code&gt; which combines the above two programs. Here’s its source:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;sh&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-sh &quot;&gt;&lt;code class=&quot;language-sh&quot; data-lang=&quot;sh&quot;&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;#!&#x2F;bin&#x2F;sh
&lt;&#x2F;span&gt;&lt;span&gt;dmenu_path &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;| &lt;&#x2F;span&gt;&lt;span&gt;dmenu &lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;$&lt;&#x2F;span&gt;&lt;span&gt;@&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot; &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;| &lt;&#x2F;span&gt;&lt;span&gt;${SHELL&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;:-&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;&#x2F;bin&#x2F;sh&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;} &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;As an aside, dmenu is a perfect example of a tool which does one thing well, and adds value
to an existing set of tools by being easily composable.&lt;&#x2F;p&gt;
&lt;p&gt;Here’s the list I see when I run it:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;daily&#x2F;dmenu-histogram&#x2F;dmenu.png&quot; alt=&quot;dmenu.png&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;And after pressing “t”:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;daily&#x2F;dmenu-histogram&#x2F;dmenu-t.png&quot; alt=&quot;dmenu-t.png&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Menu items are shown in the order they appeared on &lt;code&gt;dmenu&lt;&#x2F;code&gt;’s stdin, and &lt;code&gt;dmenu_path&lt;&#x2F;code&gt; prints
all program’s in the user’s &lt;code&gt;$PATH&lt;&#x2F;code&gt; in alphabetical order. The problem with this is there
are only a tiny handful of graphical programs that I ever want to launch with dmenu, and
it’s unlikely that they will appear early in the alphabetically-listed program names.
&lt;code&gt;dmenu_histogram&lt;&#x2F;code&gt; addresses this by maintaining a histogram of launched programs and placing
these programs in order of frequency on &lt;code&gt;dmenu&lt;&#x2F;code&gt;’s stdin before the output of &lt;code&gt;dmenu_path&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Running the &lt;code&gt;dmenu_histogram&lt;&#x2F;code&gt; script:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;daily&#x2F;dmenu-histogram&#x2F;hist.png&quot; alt=&quot;hist.png&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;…shows a list of programs I commonly use. If I press “t”…&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;daily&#x2F;dmenu-histogram&#x2F;hist-t.png&quot; alt=&quot;hist-t.png&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;…the list narrows to just the programs starting with “t”, and still shows the most
frequent programs first.&lt;&#x2F;p&gt;
&lt;p&gt;To be perfectly clear, I did not develop dmenu. I wrote myself a bash script that invokes
&lt;code&gt;dmenu&lt;&#x2F;code&gt; on the output of &lt;code&gt;dmenu_path&lt;&#x2F;code&gt; augmented with a list of common programs ordered by
frequency of use, and &lt;em&gt;updates&lt;&#x2F;em&gt; a histogram counting the number of times each program is
launched to build a more accurate list for the next invocation.&lt;&#x2F;p&gt;
&lt;p&gt;The histogram it maintains looks like this on my system at the time of writing:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;8 xterm
&lt;&#x2F;span&gt;&lt;span&gt;1 xfontsel
&lt;&#x2F;span&gt;&lt;span&gt;2 uxterm
&lt;&#x2F;span&gt;&lt;span&gt;6 st
&lt;&#x2F;span&gt;&lt;span&gt;1 vimdot
&lt;&#x2F;span&gt;&lt;span&gt;7 gimp
&lt;&#x2F;span&gt;&lt;span&gt;15 wifimgr
&lt;&#x2F;span&gt;&lt;span&gt;11 thunderbird
&lt;&#x2F;span&gt;&lt;span&gt;4 pavucontrol
&lt;&#x2F;span&gt;&lt;span&gt;1 mtpaint
&lt;&#x2F;span&gt;&lt;span&gt;2 surf
&lt;&#x2F;span&gt;&lt;span&gt;195 xlock
&lt;&#x2F;span&gt;&lt;span&gt;2 xeyes
&lt;&#x2F;span&gt;&lt;span&gt;5 virtualbox
&lt;&#x2F;span&gt;&lt;span&gt;1 dmenu
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
</description>
      </item>
      <item>
          <title>dmenu_histogram</title>
          <pubDate>Sun, 29 Nov 2020 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/dmenu_histogram/</link>
          <guid>https://www.gridbugs.org/dmenu_histogram/</guid>
          <description xml:base="https://www.gridbugs.org/dmenu_histogram/">&lt;p&gt;Of all the programs I’ve written, perhaps the one that has most directly benefited me is
&lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;gridbugs&#x2F;dotfiles&#x2F;blob&#x2F;master&#x2F;bin&#x2F;dmenu_histogram&quot;&gt;this little bash script in my dotfiles repo&lt;&#x2F;a&gt;
which runs the application launcher &lt;a href=&quot;https:&#x2F;&#x2F;tools.suckless.org&#x2F;dmenu&#x2F;&quot;&gt;dmenu&lt;&#x2F;a&gt; showing the most-frequently-used
programs first.&lt;&#x2F;p&gt;
&lt;p&gt;The typical way to run dmenu is to pipe the output of &lt;code&gt;dmenu_path&lt;&#x2F;code&gt; into &lt;code&gt;dmenu&lt;&#x2F;code&gt;.
Both commands come in the &lt;code&gt;dmenu&lt;&#x2F;code&gt; package. The former simply prints a list of all programs
in the user’s path:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;$ dmenu_path
&lt;&#x2F;span&gt;&lt;span&gt;[
&lt;&#x2F;span&gt;&lt;span&gt;2to3-2.7
&lt;&#x2F;span&gt;&lt;span&gt;2to3-3.7
&lt;&#x2F;span&gt;&lt;span&gt;aafire
&lt;&#x2F;span&gt;&lt;span&gt;aainfo
&lt;&#x2F;span&gt;&lt;span&gt;aalib-config
&lt;&#x2F;span&gt;&lt;span&gt;aasavefont
&lt;&#x2F;span&gt;&lt;span&gt;aatest
&lt;&#x2F;span&gt;&lt;span&gt;ac
&lt;&#x2F;span&gt;&lt;span&gt;accton
&lt;&#x2F;span&gt;&lt;span&gt;...
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;</description>
      </item>
      <item>
          <title>Stopping daily posts</title>
          <pubDate>Sun, 15 Nov 2020 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/daily/stopping-daily-posts/</link>
          <guid>https://www.gridbugs.org/daily/stopping-daily-posts/</guid>
          <description xml:base="https://www.gridbugs.org/daily/stopping-daily-posts/">&lt;p&gt;After just over 100 daily posts, I’m going to relax the constraint that they be written each day.
I’m struggling to come up with interesting content, and writing daily posts is starting to feel like a chore.
I’ll keep this area of the site as a place where I can write relatively low-effort, short posts, but I’ll
only write them on days when I have something compelling to say.&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Disk image tool progress - Counter-intuitive header population</title>
          <pubDate>Fri, 13 Nov 2020 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/daily/disk-image-tool-progress-counter-intuitive-header-population/</link>
          <guid>https://www.gridbugs.org/daily/disk-image-tool-progress-counter-intuitive-header-population/</guid>
          <description xml:base="https://www.gridbugs.org/daily/disk-image-tool-progress-counter-intuitive-header-population/">&lt;p&gt;While preparing to implement a disk image creation tool I built a few tools for
reading files from inside disk images. In the process I wrote some code for
parsing the various disk and partition header structures present in a disk
image.  Now that I’m actually generating images of my own, I need to populate
headers in the images.&lt;&#x2F;p&gt;
&lt;p&gt;The most obvious way to do this is to populate the fields of the same header
type that I previously parsed from disk images when reading files from them,
and then have some additional code for serializing the header type into the
disk image in whatever format the GPT or FAT protocols expect.&lt;&#x2F;p&gt;
&lt;p&gt;After going a little way down this path I stopped and tried an alternative,
where I write header information directly in its serialized form to a buffer,
and then parse it back out with my existing header parsers, purely as a sanity
check. There are two benefits to this approach.  The first is the sanity check&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;I’ve tested the header parsing code on real disk images and have some degree
of confidence that it works. In addition to parsing values from disk headers,
it also performs some validation, such as checking checksums.  Writing the disk
header directly and parsing it back means I can check that the header I’m
writing to disk is valid.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;The second benefit is less code. Were I to populate a struct with header
fields, and then serialize it, I’d have 3 places where each field is listed out
in the code - parsing headers, populating header structs, and serializing
header structs. As it is now, there are only two - parsing headers, and writing
header fields.&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Disk image tool progress - directory hierarchies</title>
          <pubDate>Thu, 12 Nov 2020 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/daily/disk-image-tool-progress-directory-hierarchies/</link>
          <guid>https://www.gridbugs.org/daily/disk-image-tool-progress-directory-hierarchies/</guid>
          <description xml:base="https://www.gridbugs.org/daily/disk-image-tool-progress-directory-hierarchies/">&lt;p&gt;I finally mustered to motivation to get some work done on my
&lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;gridbugs&#x2F;gpt-fat-disk-image&quot;&gt;disk image creation tool&lt;&#x2F;a&gt;.
Tonight I prepared for writing the FAT partition to the image by constructing
a representation of the directory hierarchy implied by the list of paths
to all files that will be created in the disk image.
Currently the hierarchy is only used to determine the partition size.
The next step will be to also use it to populate the FAT table and clusters
that make up a FAT partition.&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Learning about kqueue</title>
          <pubDate>Wed, 11 Nov 2020 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/daily/learning-about-kqueue/</link>
          <guid>https://www.gridbugs.org/daily/learning-about-kqueue/</guid>
          <description xml:base="https://www.gridbugs.org/daily/learning-about-kqueue/">&lt;p&gt;I’m trying to fix a bug in the &lt;code&gt;rb-kqueue&lt;&#x2F;code&gt; gem which occasionally
&lt;a href=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;daily&#x2F;building-this-site-sometimes-crashes-ruby&#x2F;&quot;&gt;crashes the ruby interpreter&lt;&#x2F;a&gt;
when I’m running a file-watching server while working on this site.&lt;&#x2F;p&gt;
&lt;p&gt;The bug seems to be in the &lt;code&gt;rb-kqueue&lt;&#x2F;code&gt; gem, which is a thin wrapper around *BSD’s kqueue
API, which is an interface for subscribing to notifications when a file changes, similar
to Linux’s inotify.&lt;&#x2F;p&gt;
&lt;p&gt;To understand what’s wrong with &lt;code&gt;rb-kqueue&lt;&#x2F;code&gt;, I’m going to start by learning how to make
the most basic thing that does what &lt;code&gt;rb-kqueue&lt;&#x2F;code&gt; does. To start, forget ruby, and makd something
simple in c.&lt;&#x2F;p&gt;
&lt;p&gt;Here’s how to get started with kqueue:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;$ man kqueue
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;…and scroll down to the “EXAMPLES” section.
Copy paste the example code into a c file, compile it, and run it.
It will watch the file specified as an argument and print a message when the watched file changes.&lt;&#x2F;p&gt;
&lt;p&gt;Then read the manual page and modify the code to learn more.&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Configuring Feh</title>
          <pubDate>Tue, 10 Nov 2020 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/daily/configuring-feh/</link>
          <guid>https://www.gridbugs.org/daily/configuring-feh/</guid>
          <description xml:base="https://www.gridbugs.org/daily/configuring-feh/">&lt;p&gt;In lieu of a file listing configuration settings, the image viewer &lt;code&gt;feh&lt;&#x2F;code&gt; does something
I’ve never seen anywhere else. The config file lists “themes”, which are named sets of arguments.
You can list as many themes as you like, giving each a unique name.&lt;&#x2F;p&gt;
&lt;p&gt;My list:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;# ~&#x2F;.config&#x2F;feh&#x2F;themes
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;fehp --keep-zoom-vp --force-aliasing --draw-filename --draw-tinted
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;I only have one theme named “fehp” (the p stands for “pixelated”).
To use the theme, make a symlink to the &lt;code&gt;feh&lt;&#x2F;code&gt; executable, and name the symlink after the theme:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;$ ln -s $(which feh) ~&#x2F;bin&#x2F;fehp
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Now I can run &lt;code&gt;fehp&lt;&#x2F;code&gt; and it will be equivalent to running:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;$ feh --keep-zoom-vp --force-aliasing --draw-filename --draw-tinted
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
</description>
      </item>
      <item>
          <title>Chargrid HiDPI Scaling Fix</title>
          <pubDate>Mon, 09 Nov 2020 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/daily/chargrid-hidpi-scaling-fix/</link>
          <guid>https://www.gridbugs.org/daily/chargrid-hidpi-scaling-fix/</guid>
          <description xml:base="https://www.gridbugs.org/daily/chargrid-hidpi-scaling-fix/">&lt;p&gt;I spent some time over the weekend trying to reproduce a bug where
&lt;a href=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;daily&#x2F;winit-makes-windows-the-wrong-size-in-some-cases-i-think&#x2F;&quot;&gt;winit windows end up the wrong size due to rapid scaling factor changes&lt;&#x2F;a&gt;.
Ultimately I couldn’t reproduce the problem (but I’m keeping an eye out) but what I did learn is that when the
mouse cursor is on the hidpi monitor (I have two - one hidpi and one normal-dpi), and a windows opens on the non-hidip
monitor, it starts with the hidip scaling factor, but soon after starting its scaling factor changes back to 1.
I found that &lt;a href=&quot;https:&#x2F;&#x2F;crates.io&#x2F;crates&#x2F;chargrid_graphical&quot;&gt;chargrid_graphical&lt;&#x2F;a&gt; wasn’t handling the scaling factor change
(only the accompanying window size change), which caused graphics to appear blurry after the resize. It’s now fixed.&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Ruby shell with custom .inputrc</title>
          <pubDate>Sun, 08 Nov 2020 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/daily/ruby-shell-with-custom-inputrc/</link>
          <guid>https://www.gridbugs.org/daily/ruby-shell-with-custom-inputrc/</guid>
          <description xml:base="https://www.gridbugs.org/daily/ruby-shell-with-custom-inputrc/">&lt;p&gt;The program &lt;code&gt;irb&lt;&#x2F;code&gt; is an interactive ruby shell.
When I run it, the VI navigation keys “HJKL” move the cursor rather than inserting the characters “HJKL”.
This looks related to my custom ~&#x2F;.inputrc file which specifies readline use the VI keybindings, instead of
the default emacs keybindings:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;# ~&#x2F;.inputrc
&lt;&#x2F;span&gt;&lt;span&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;set editing-mode vi
&lt;&#x2F;span&gt;&lt;span&gt;...
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;In certain interactive shells (those that use readline), such as bash and the python shell, this adds
modal line editing and VI key navigation that I’m accustomed to in Vim.
But it seems that &lt;code&gt;irb&lt;&#x2F;code&gt; doesn’t work correctly with VI editing mode.
It’s trying to do &lt;em&gt;something&lt;&#x2F;em&gt;. Switching from insert to normal mode works, and I can use additional
VI navigation commands such as work navigation while in normal mode. But “HJKL” navigation doesn’t
turn off in insert mode, so I can’t type “HJKL”.&lt;&#x2F;p&gt;
&lt;p&gt;The workaround is to tell &lt;code&gt;irb&lt;&#x2F;code&gt; to ignore ~&#x2F;.inputrc when launching it:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;$ INPUTRC=&#x2F;dev&#x2F;null irb
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
</description>
      </item>
      <item>
          <title>Tmux runs a login shell by default</title>
          <pubDate>Sat, 07 Nov 2020 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/daily/tmux-runs-a-login-shell-by-default/</link>
          <guid>https://www.gridbugs.org/daily/tmux-runs-a-login-shell-by-default/</guid>
          <description xml:base="https://www.gridbugs.org/daily/tmux-runs-a-login-shell-by-default/">&lt;p&gt;Unless configured otherwise, shells running in tmux panes are login shells.
This means that if you start tmux from a shell whose environment already reflects
your (say) .profile script, you’ll end up sourcing it a second time leading to
problems highlighted &lt;a href=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;daily&#x2F;make-sure-your-terminal-emulator-runs-in-the-expected-environment&#x2F;&quot;&gt;here&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;To have tmux run a non-login shell, add this to .tmux.conf:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;set -g default-command &amp;quot;${SHELL}&amp;quot;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
</description>
      </item>
      <item>
          <title>Building this site sometimes crashes ruby</title>
          <pubDate>Fri, 06 Nov 2020 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/daily/building-this-site-sometimes-crashes-ruby/</link>
          <guid>https://www.gridbugs.org/daily/building-this-site-sometimes-crashes-ruby/</guid>
          <description xml:base="https://www.gridbugs.org/daily/building-this-site-sometimes-crashes-ruby/">&lt;p&gt;This is a jekyll site, which means that it’s composed of a bunch of markdown files,
and I run a ruby script called “jekyll” which turns it into a bunch of html files
which I then upload to my web server. When working on a post, I usually run:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;bundle exec jekyll serve
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;…which watches the markdown files, regenerating the
html each time a file changes, and serves the result on a local we server.
When I first switched to FreeBSD, the file-watching mechanism wasn’t working properly,
and I would see:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;          NoMethodError:
&lt;&#x2F;span&gt;&lt;span&gt;            undefined method `callback!&amp;#39; for nil:NilClass
&lt;&#x2F;span&gt;&lt;span&gt;          # .&#x2F;vendor&#x2F;bundle&#x2F;ruby&#x2F;2.6&#x2F;gems&#x2F;rb-kqueue-0.2.5&#x2F;lib&#x2F;rb-kqueue&#x2F;event.rb:80:in `callback!&amp;#39;
&lt;&#x2F;span&gt;&lt;span&gt;          # .&#x2F;vendor&#x2F;bundle&#x2F;ruby&#x2F;2.6&#x2F;gems&#x2F;rb-kqueue-0.2.5&#x2F;lib&#x2F;rb-kqueue&#x2F;queue.rb:337:in `block in process&amp;#39;
&lt;&#x2F;span&gt;&lt;span&gt;          # .&#x2F;vendor&#x2F;bundle&#x2F;ruby&#x2F;2.6&#x2F;gems&#x2F;rb-kqueue-0.2.5&#x2F;lib&#x2F;rb-kqueue&#x2F;queue.rb:337:in `each&amp;#39;
&lt;&#x2F;span&gt;&lt;span&gt;          # .&#x2F;vendor&#x2F;bundle&#x2F;ruby&#x2F;2.6&#x2F;gems&#x2F;rb-kqueue-0.2.5&#x2F;lib&#x2F;rb-kqueue&#x2F;queue.rb:337:in `process&amp;#39;
&lt;&#x2F;span&gt;&lt;span&gt;          # .&#x2F;vendor&#x2F;bundle&#x2F;ruby&#x2F;2.6&#x2F;gems&#x2F;rb-kqueue-0.2.5&#x2F;lib&#x2F;rb-kqueue&#x2F;queue.rb:316:in `run&amp;#39;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;…and the site wouldn’t rebuild when a file changed. This is a &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;guard&#x2F;listen&#x2F;issues&#x2F;475&quot;&gt;known issue&lt;&#x2F;a&gt;,
and I could work around it by polling instead of using FreeBSD’s kqueue mechanism to watch files, but
the latency was quite high. I eventually &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;mat813&#x2F;rb-kqueue&#x2F;pull&#x2F;12&quot;&gt;fixed&lt;&#x2F;a&gt; a bug in the
rb-kqueue gem which seemed to solve the problem. It’s not merged yet but I can include a reference to my
fork of the library in this site’s Gemfile until it gets merged.&lt;&#x2F;p&gt;
&lt;p&gt;Lately however, I’ve started seeing this crash when changing a file:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;&#x2F;usr&#x2F;home&#x2F;steve&#x2F;.rvm&#x2F;gems&#x2F;ruby-2.7.2&#x2F;bundler&#x2F;gems&#x2F;rb-kqueue-144ee7bb7963&#x2F;lib&#x2F;rb-kqueue&#x2F;native&#x2F;flags.rb:145: [BUG] Segmentation fault at 0x0000000100210014
&lt;&#x2F;span&gt;&lt;span&gt;ruby 2.7.2p137 (2020-10-01 revision 5445e04352) [x86_64-freebsd12.2]
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;…which is clearly related to rb-kqueue. I’m not sure if this was introduced by my change.
Need to dig deeper!&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>How I start X</title>
          <pubDate>Thu, 05 Nov 2020 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/daily/how-i-start-x/</link>
          <guid>https://www.gridbugs.org/daily/how-i-start-x/</guid>
          <description xml:base="https://www.gridbugs.org/daily/how-i-start-x/">&lt;p&gt;On FreeBSD and Linux I start X with &lt;code&gt;startx&lt;&#x2F;code&gt;. I don’t use a display manager, instead electing to
login directly to a tty and start X explicitly. Over the years I’ve tried out a bunch of display
managers. I used SLIM while it existed, and LXDM after that, but at some point I decided that
the complexity of configuring a display manager to start X just the way I like meant more work
than typing &lt;code&gt;startx&lt;&#x2F;code&gt; the first time I login to my computer. I maintain a .xinitrc file which
performs some setup and starts my window manager of choice - dwm.&lt;&#x2F;p&gt;
&lt;p&gt;On OpenBSD, which I occasionally experiment with, there is no &lt;code&gt;startx&lt;&#x2F;code&gt; command because something
something security. OpenBSD comes with a display manager called “xenodm” (if you select it
during installation), which presents you with a graphical login form, then starts X by running
a file called “.xsession” in your home directory. My .session looks like this:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;. ~&#x2F;.profile
&lt;&#x2F;span&gt;&lt;span&gt;. ~&#x2F;.xinitrc
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;It sources .profile to set environment variables for the window manager, then sources .xinitrc
to start the window manager as per my carefully crafted configuration. Simple!&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Make sure your terminal emulator runs in the expected environment</title>
          <pubDate>Wed, 04 Nov 2020 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/daily/make-sure-your-terminal-emulator-runs-in-the-expected-environment/</link>
          <guid>https://www.gridbugs.org/daily/make-sure-your-terminal-emulator-runs-in-the-expected-environment/</guid>
          <description xml:base="https://www.gridbugs.org/daily/make-sure-your-terminal-emulator-runs-in-the-expected-environment/">&lt;p&gt;Unix processes run in an environment consisting of string values assigned to named environment variables.
Some processes such as a shell or window manager are responsible for launching additional processes.
By convention, new processes inherit the environment variables of the process that created them.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;code&gt;$PATH&lt;&#x2F;code&gt; is a particularly interesting variable because it’s usually updated by concatenating a list of directories
with the original value of &lt;code&gt;$PATH&lt;&#x2F;code&gt;. A conventional place to update &lt;code&gt;$PATH&lt;&#x2F;code&gt; is a “profile” script (usually .profile or
.bash_profile), which is run when a login shell starts.
Avoid sourcing this script multiple times, as the &lt;code&gt;$PATH&lt;&#x2F;code&gt; variable will grow larger and larger, as more (duplicate) values are concatenated onto it.
As long as a login shell is run once when you log in to your machine, and all other processes
(including other shells) are descendants of the login shell, your &lt;code&gt;$PATH&lt;&#x2F;code&gt; will remain what it was set to when the login shell started.&lt;&#x2F;p&gt;
&lt;p&gt;Here’s my &lt;code&gt;$PATH&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;+steve@fontaine ~ $ echo $PATH
&lt;&#x2F;span&gt;&lt;span&gt;&#x2F;home&#x2F;steve&#x2F;.cargo&#x2F;bin:&#x2F;home&#x2F;steve&#x2F;bin:&#x2F;home&#x2F;steve&#x2F;.bin:&#x2F;home&#x2F;steve&#x2F;.local&#x2F;bin:&#x2F;home&#x2F;steve&#x2F;.local&#x2F;sbin:
&lt;&#x2F;span&gt;&lt;span&gt;&#x2F;sbin:&#x2F;bin:&#x2F;usr&#x2F;sbin:&#x2F;usr&#x2F;bin:&#x2F;usr&#x2F;local&#x2F;sbin:&#x2F;usr&#x2F;local&#x2F;bin:&#x2F;home&#x2F;steve&#x2F;bin:&#x2F;home&#x2F;steve&#x2F;.rvm&#x2F;bin
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Run &lt;code&gt;bash&lt;&#x2F;code&gt; to start a new shell as a child of the current shell:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;+steve@fontaine ~ $ bash
&lt;&#x2F;span&gt;&lt;span&gt;+steve@fontaine ~ $ echo $PATH
&lt;&#x2F;span&gt;&lt;span&gt;&#x2F;home&#x2F;steve&#x2F;.cargo&#x2F;bin:&#x2F;home&#x2F;steve&#x2F;bin:&#x2F;home&#x2F;steve&#x2F;.bin:&#x2F;home&#x2F;steve&#x2F;.local&#x2F;bin:&#x2F;home&#x2F;steve&#x2F;.local&#x2F;sbin:
&lt;&#x2F;span&gt;&lt;span&gt;&#x2F;sbin:&#x2F;bin:&#x2F;usr&#x2F;sbin:&#x2F;usr&#x2F;bin:&#x2F;usr&#x2F;local&#x2F;sbin:&#x2F;usr&#x2F;local&#x2F;bin:&#x2F;home&#x2F;steve&#x2F;bin:&#x2F;home&#x2F;steve&#x2F;.rvm&#x2F;bin
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Note that the &lt;code&gt;$PATH&lt;&#x2F;code&gt; wasn’t affected. My .profile script didn’t run.&lt;&#x2F;p&gt;
&lt;p&gt;Now run &lt;code&gt;bash --login&lt;&#x2F;code&gt; which starts a new login shell as a child of the current shell:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;+steve@fontaine ~ $ bash --login
&lt;&#x2F;span&gt;&lt;span&gt;+steve@fontaine ~ $ echo $PATH
&lt;&#x2F;span&gt;&lt;span&gt;&#x2F;home&#x2F;steve&#x2F;.cargo&#x2F;bin:&#x2F;home&#x2F;steve&#x2F;bin:&#x2F;home&#x2F;steve&#x2F;.bin:&#x2F;home&#x2F;steve&#x2F;.local&#x2F;bin:&#x2F;home&#x2F;steve&#x2F;.local&#x2F;sbin:
&lt;&#x2F;span&gt;&lt;span&gt;&#x2F;home&#x2F;steve&#x2F;.cargo&#x2F;bin:&#x2F;home&#x2F;steve&#x2F;bin:&#x2F;home&#x2F;steve&#x2F;.bin:&#x2F;home&#x2F;steve&#x2F;.local&#x2F;bin:&#x2F;home&#x2F;steve&#x2F;.local&#x2F;sbin:
&lt;&#x2F;span&gt;&lt;span&gt;&#x2F;sbin:&#x2F;bin:&#x2F;usr&#x2F;sbin:&#x2F;usr&#x2F;bin:&#x2F;usr&#x2F;local&#x2F;sbin:&#x2F;usr&#x2F;local&#x2F;bin:&#x2F;home&#x2F;steve&#x2F;bin:&#x2F;home&#x2F;steve&#x2F;.rvm&#x2F;bin:&#x2F;home&#x2F;steve&#x2F;.rvm&#x2F;bin
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This time my &lt;code&gt;$PATH&lt;&#x2F;code&gt; got longer, as all the directories I add when a login shell starts got added &lt;em&gt;again&lt;&#x2F;em&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;This scenario was contrived, but similar problems can arise in practice, in particular through the use of a window manager (WM).&lt;&#x2F;p&gt;
&lt;p&gt;How do you launch your window manager? The two common ways are running &lt;code&gt;startx&lt;&#x2F;code&gt; from a tty, and logging in via a display manager, which
presents a graphical login form and then starts your window manager after login. Much like a text shell (such as bash), your window manager
creates new processes. &lt;code&gt;$PATH&lt;&#x2F;code&gt; is of particular relevance to terminal emulators, so let’s only consider terminal emulators and the shells that run
“inside” them. Whether you’re double-clicking on an icon, using a launcher like dmenu, or have configured your window manager to directly launch
a terminal emulator in response to a key combination, the windows manager is starting your terminal emulator, which in turn is starting a shell.&lt;&#x2F;p&gt;
&lt;p&gt;Whenever you start a shell, your goal is to have your &lt;code&gt;$PATH&lt;&#x2F;code&gt; be set to whatever your .profile script specifies.
Something is amiss if &lt;code&gt;$PATH&lt;&#x2F;code&gt; has the settings in .profile applied multiple times (as in the example above), or not at all in which case
none of your custom executable directories will work.&lt;&#x2F;p&gt;
&lt;p&gt;If you start your WM with &lt;code&gt;startx&lt;&#x2F;code&gt;, then you must have logged into a tty directly
which will have started a login shell. Running &lt;code&gt;startx&lt;&#x2F;code&gt; from a login shell will result in your intended &lt;code&gt;$PATH&lt;&#x2F;code&gt; being part of your WM’s
environment (WMs are unix processes too; they have environments), and when your WM launched a shell (via a terminal emulator)
the shell inherited the correct &lt;code&gt;$PATH&lt;&#x2F;code&gt; too and you’re done.&lt;&#x2F;p&gt;
&lt;p&gt;If you use a display manager to login, somehow by the time you launch a shell, you’re .profile script needs to have been invoked &lt;em&gt;exactly once&lt;&#x2F;em&gt; (so your &lt;code&gt;$PATH&lt;&#x2F;code&gt;
is set the way you like). This can be done from within the WM itself. Most terminal emulators take a flag which instructs them to launch a login
shell rather than a regular shell. Have your WM pass this flag when launching a terminal. If you were to start a second terminal from the first
(e.g. tmux), it would just launch a regular (non-login) shell, so the duplicate &lt;code&gt;$PATH&lt;&#x2F;code&gt; problem is avoided. The only gotcha here is if you ever
start the WM with startx and not a display manager, WM-spawned terminal emulators will have sourced your .profile script twice
(once from the login shell you got when logging in to a tty, and a second time as you instructed your WM to launch a terminal emulator with a login shell inside).
A less fragile approach is to configure the display manager to setup your environment before launching the WM, however unfortunately not all display managers can
be thus configured.&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Make sure your terminal emulator runs in the expected environment</title>
          <pubDate>Wed, 04 Nov 2020 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/make-sure-your-terminal-emulator-runs-in-the-expected-environment/</link>
          <guid>https://www.gridbugs.org/make-sure-your-terminal-emulator-runs-in-the-expected-environment/</guid>
          <description xml:base="https://www.gridbugs.org/make-sure-your-terminal-emulator-runs-in-the-expected-environment/">&lt;p&gt;This is a follow-up to &lt;a href=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;why-you-need-a-.bashrc-and-.profile&#x2F;&quot;&gt;Why you need a .bashrc and .profile&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Unix processes run in an environment consisting of string values assigned to named environment variables.
Some processes such as a shell or window manager are responsible for launching additional processes.
By convention, new processes inherit the environment variables of the process that created them.&lt;&#x2F;p&gt;</description>
      </item>
      <item>
          <title>Winit makes windows the wrong size in some cases I think</title>
          <pubDate>Tue, 03 Nov 2020 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/daily/winit-makes-windows-the-wrong-size-in-some-cases-i-think/</link>
          <guid>https://www.gridbugs.org/daily/winit-makes-windows-the-wrong-size-in-some-cases-i-think/</guid>
          <description xml:base="https://www.gridbugs.org/daily/winit-makes-windows-the-wrong-size-in-some-cases-i-think/">&lt;p&gt;I want to find some time to debug a problem with &lt;a href=&quot;https:&#x2F;&#x2F;crates.io&#x2F;crates&#x2F;chargrid_graphical&quot;&gt;chargrid_graphical&lt;&#x2F;a&gt;
where new windows spend a couple of frames at their specified size before shrinking. It seems related to hi-dpi
scaling. In particular, when a program starts, I observe the hi-dpi scaling factor starting as 1, then briefly changing
to 1.6667, then back to 1 again after several milliseconds. My hypothesis is that there is a race condition in
&lt;a href=&quot;https:&#x2F;&#x2F;crates.io&#x2F;crates&#x2F;winit&quot;&gt;winit&lt;&#x2F;a&gt; - the de-facto standard windows creation library for rust.
The scaling factor change from 1 to 1.6667 should have caused the window to get larger, but logging the size of the
window on each frame reveals that this never happens. The change from 1.6667 back to 1 does cause it to get smaller,
but it ends up 1&#x2F;1.6667 of its original size. This suggests that winit is telling X11 (in my case) to resize the window,
and also querying X11 for windows sizes, and that there is a delay between telling X11 to resize and the change being
affected, during which X11 still reports the old size.&lt;&#x2F;p&gt;
&lt;p&gt;As to why new windows briefly have their scaling factor set to 1.6667, I have no idea! It’s possibly related to
my multi-monitor setup, where one monitor is hi-dpi and the other is not. This behaviour only exhibits on the
non-hi-dpi monitor.&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Why you need a .bashrc and .profile</title>
          <pubDate>Mon, 02 Nov 2020 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/daily/why-you-need-a-bashrc-and-profile/</link>
          <guid>https://www.gridbugs.org/daily/why-you-need-a-bashrc-and-profile/</guid>
          <description xml:base="https://www.gridbugs.org/daily/why-you-need-a-bashrc-and-profile/">&lt;p&gt;I’ve been doing this wrong my whole life.&lt;&#x2F;p&gt;
&lt;p&gt;For as long as I can remember I’ve included some version of the following line in my .bashrc:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;bash&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-bash &quot;&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;export &lt;&#x2F;span&gt;&lt;span&gt;PATH&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;$&lt;&#x2F;span&gt;&lt;span&gt;HOME&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&#x2F;bin:$&lt;&#x2F;span&gt;&lt;span&gt;HOME&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&#x2F;.bin:$&lt;&#x2F;span&gt;&lt;span&gt;HOME&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&#x2F;.local&#x2F;bin:$&lt;&#x2F;span&gt;&lt;span&gt;HOME&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&#x2F;.local&#x2F;sbin:$&lt;&#x2F;span&gt;&lt;span&gt;PATH&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This line defines a few directories inside my home directory where I can place executable
files which can be run without their full path. The problem this causes is when starting
a terminal from an existing terminal session (launching &lt;code&gt;tmux&lt;&#x2F;code&gt;, or launching a graphical
terminal from an X11 session started with &lt;code&gt;startx&lt;&#x2F;code&gt;), I end up with an unfortunate-looking &lt;code&gt;PATH&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;p&gt;&#x2F;home&#x2F;steve&#x2F;bin:&#x2F;home&#x2F;steve&#x2F;.bin:&#x2F;home&#x2F;steve&#x2F;.local&#x2F;bin:&#x2F;home&#x2F;steve&#x2F;.local&#x2F;sbin:&#x2F;home&#x2F;steve&#x2F;bin:&#x2F;home&#x2F;steve&#x2F;.bin:&#x2F;home&#x2F;steve&#x2F;.local&#x2F;bin:&#x2F;home&#x2F;steve&#x2F;.local&#x2F;sbin:&#x2F;home&#x2F;steve&#x2F;.local&#x2F;bin:&#x2F;home&#x2F;steve&#x2F;.bin:&#x2F;home&#x2F;steve&#x2F;bin:&#x2F;home&#x2F;steve&#x2F;bin:&#x2F;home&#x2F;steve&#x2F;.bin:&#x2F;home&#x2F;steve&#x2F;.local&#x2F;bin:&#x2F;home&#x2F;steve&#x2F;.local&#x2F;sbin:&#x2F;sbin:&#x2F;bin:&#x2F;usr&#x2F;sbin:&#x2F;usr&#x2F;bin:&#x2F;usr&#x2F;local&#x2F;sbin:&#x2F;usr&#x2F;local&#x2F;bin&lt;&#x2F;p&gt;
&lt;p&gt;That was from a tmux session that had been launched from a graphical terminal emulator launched by dmenu running
in an X11 session that I started by running &lt;code&gt;startx&lt;&#x2F;code&gt; from a tty. Each time bash started, it would prepend &lt;code&gt;$HOME&#x2F;bin&#x2F;:...&lt;&#x2F;code&gt;
to the &lt;code&gt;PATH&lt;&#x2F;code&gt; variable, causing it to build up to the monstrosity above.&lt;&#x2F;p&gt;
&lt;p&gt;The solution: login shells and .profile.&lt;&#x2F;p&gt;
&lt;p&gt;Shells like bash can be run as “login shells”, which causes them to behave a little differently to usual.
Of note, the .bashrc file is not sourced when bash is run as a login shell (other shells behave similarly with their respective rc files).
In lieu of .bashrc, the shell will source .profile (or .bash_profile).&lt;&#x2F;p&gt;
&lt;p&gt;The first shell you encounter when beginning a session will typically be a login shell.
When logging in to a tty, or ssh-ing into a machine, you’ll find yourself in a login shell.
Subsequently started shells will generally not be login shells unless this is explicitly specified.
Thus the .profile file is the place for all per-session configuration, such as setting &lt;code&gt;PATH&lt;&#x2F;code&gt; and other environment variables.
All the per &lt;em&gt;shell&lt;&#x2F;em&gt; configurations, such as aliases, functions, or the prompt, belong in .bashrc.
This is because unlike environment variables, these objects aren’t inherited by nested shells; they must be defined again
in each new shell.&lt;&#x2F;p&gt;
&lt;p&gt;Since login shells are still shells, they should behave the same way as a regular shell from the user’s point of
view. This means all the settings from .bashrc should still be applied. This it is typical to place the following the
start of .profile:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;bash&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-bash &quot;&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;if &lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;[ &lt;&#x2F;span&gt;&lt;span&gt;-n &lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;$&lt;&#x2F;span&gt;&lt;span&gt;BASH_VERSION&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;]&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;; then
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;if &lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;[ &lt;&#x2F;span&gt;&lt;span&gt;-f &lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;$&lt;&#x2F;span&gt;&lt;span&gt;HOME&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&#x2F;.bashrc&amp;quot; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;]&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;; then
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;. &lt;&#x2F;span&gt;&lt;span&gt;~&#x2F;.bashrc
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;fi
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;fi
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
</description>
      </item>
      <item>
          <title>Why you need a .bashrc and .profile</title>
          <pubDate>Mon, 02 Nov 2020 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/why-you-need-a-.bashrc-and-.profile/</link>
          <guid>https://www.gridbugs.org/why-you-need-a-.bashrc-and-.profile/</guid>
          <description xml:base="https://www.gridbugs.org/why-you-need-a-.bashrc-and-.profile/">&lt;p&gt;I’ve been doing this wrong my whole life.&lt;&#x2F;p&gt;
&lt;p&gt;For as long as I can remember I’ve included some version of the following line in my .bashrc:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;bash&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-bash &quot;&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;export &lt;&#x2F;span&gt;&lt;span&gt;PATH&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;$&lt;&#x2F;span&gt;&lt;span&gt;HOME&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&#x2F;bin:$&lt;&#x2F;span&gt;&lt;span&gt;HOME&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&#x2F;.bin:$&lt;&#x2F;span&gt;&lt;span&gt;HOME&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&#x2F;.local&#x2F;bin:$&lt;&#x2F;span&gt;&lt;span&gt;HOME&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&#x2F;.local&#x2F;sbin:$&lt;&#x2F;span&gt;&lt;span&gt;PATH&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;</description>
      </item>
      <item>
          <title>Updating Library Dependencies</title>
          <pubDate>Sun, 01 Nov 2020 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/daily/updating-library-dependencies/</link>
          <guid>https://www.gridbugs.org/daily/updating-library-dependencies/</guid>
          <description xml:base="https://www.gridbugs.org/daily/updating-library-dependencies/">&lt;p&gt;Every so often I update the deps of my rust libraries.  This is so that crates
depending on my libraries and also my libraries’ dependencies don’t end up
depending on multiple different versions of the same thing. Doing so wouldn’t
cause any problems in practice; cargo can handle this situation perfectly fine,
but it adds to build&#x2F;download times and binary sizes.&lt;&#x2F;p&gt;
&lt;p&gt;Today I updated the &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;gridbugs&#x2F;wfc&quot;&gt;wfc&lt;&#x2F;a&gt;. The only change
required by the core &lt;a href=&quot;https:&#x2F;&#x2F;crates.io&#x2F;crates&#x2F;wfc&quot;&gt;wfc&lt;&#x2F;a&gt; crate was bumping
&lt;a href=&quot;https:&#x2F;&#x2F;crates.io&#x2F;crates&#x2F;hashbrown&quot;&gt;hashbrown&lt;&#x2F;a&gt; from 0.7 to 0.9.
I also updated the examples &lt;a href=&quot;https:&#x2F;&#x2F;crates.io&#x2F;crates&#x2F;wfc_image&quot;&gt;wfc_image&lt;&#x2F;a&gt; crate
to render animations with &lt;a href=&quot;https:&#x2F;&#x2F;crates.io&#x2F;crates&#x2F;pixels&quot;&gt;pixels&lt;&#x2F;a&gt; instead
of &lt;a href=&quot;https:&#x2F;&#x2F;crates.io&#x2F;crates&#x2F;pixel_grid&quot;&gt;pixel_grid&lt;&#x2F;a&gt;, and to parse arguments with
&lt;a href=&quot;https:&#x2F;&#x2F;crates.io&#x2F;crates&#x2F;meap&quot;&gt;meap&lt;&#x2F;a&gt; instead of &lt;a href=&quot;https:&#x2F;&#x2F;crates.io&#x2F;crates&#x2F;simon&quot;&gt;simon&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>How I spent 2020-10-31</title>
          <pubDate>Sat, 31 Oct 2020 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/daily/how-i-spent-2020-10-31/</link>
          <guid>https://www.gridbugs.org/daily/how-i-spent-2020-10-31/</guid>
          <description xml:base="https://www.gridbugs.org/daily/how-i-spent-2020-10-31/">&lt;p&gt;I’m going to start occasionally posting lists of non-technical things I did on particular days
to save time on days when I have nothing technical or insightful to share here.&lt;&#x2F;p&gt;
&lt;p&gt;Today I:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;ate brunch at a cafe in Surry Hills&lt;&#x2F;li&gt;
&lt;li&gt;walked to Centennial Park to look at birds&lt;&#x2F;li&gt;
&lt;li&gt;got caught in a thunderstorm&lt;&#x2F;li&gt;
&lt;li&gt;watch a Halloween screening of a movie at a cinema in Newtown&lt;&#x2F;li&gt;
&lt;li&gt;played a one-shot table-top RPG created by a friend, that they were running for the first time&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
</description>
      </item>
      <item>
          <title>FreeBSD 12.2 intel graphics bug</title>
          <pubDate>Fri, 30 Oct 2020 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/daily/freebsd-12-2-intel-graphics-bug/</link>
          <guid>https://www.gridbugs.org/daily/freebsd-12-2-intel-graphics-bug/</guid>
          <description xml:base="https://www.gridbugs.org/daily/freebsd-12-2-intel-graphics-bug/">&lt;p&gt;I updated my laptop to FreeBSD 12.2 and could no longer start X.
My laptop is a Lenovo Thinkpad T470 with intel graphics.&lt;&#x2F;p&gt;
&lt;p&gt;The quick and dirty workaround was to configure X to use the vesa driver
rather than the intel graphics driver.&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;# &#x2F;usr&#x2F;local&#x2F;etc&#x2F;X11&#x2F;xorg.conf.d&#x2F;driver-intel.conf
&lt;&#x2F;span&gt;&lt;span&gt;Section &amp;quot;Device&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;    Identifier &amp;quot;Card0&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;    Driver     &amp;quot;vesa&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;    # Driver     &amp;quot;intel&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;    # Option     &amp;quot;DRI&amp;quot; &amp;quot;3&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;EndSection
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;After this change I could start X, but the vesa driver is slow.&lt;&#x2F;p&gt;
&lt;p&gt;I found the fix in &lt;a href=&quot;https:&#x2F;&#x2F;twitter.com&#x2F;vermaden&#x2F;status&#x2F;1321375859429773312&quot;&gt;a tweet&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;The problematic package is drm-kmod. The fix is to rebuild it from the ports tree.&lt;&#x2F;p&gt;
&lt;p&gt;Instructions copied from tweet:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;# svnlite release
&lt;&#x2F;span&gt;&lt;span&gt;# portsnap auto
&lt;&#x2F;span&gt;&lt;span&gt;# make -C &#x2F;usr&#x2F;ports&#x2F;graphics&#x2F;drm-fbsd12.0-kmod build deinstall install clean
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Switch the X config to use the intel driver again, and reboot, and X will be working again
with the intel driver.&lt;&#x2F;p&gt;
&lt;p&gt;Here’s the contents of my &#x2F;usr&#x2F;local&#x2F;etc&#x2F;svnup.conf:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;# $FreeBSD$
&lt;&#x2F;span&gt;&lt;span&gt;#
&lt;&#x2F;span&gt;&lt;span&gt;# Default configuration options for svnup.conf.
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;[defaults]
&lt;&#x2F;span&gt;&lt;span&gt;work_directory=&#x2F;var&#x2F;tmp&#x2F;svnup
&lt;&#x2F;span&gt;&lt;span&gt;host=svn.freebsd.org
&lt;&#x2F;span&gt;&lt;span&gt;#host=svn0.us-west.freebsd.org
&lt;&#x2F;span&gt;&lt;span&gt;#host=svn0.us-east.freebsd.org
&lt;&#x2F;span&gt;&lt;span&gt;#host=svn0.eu.freebsd.org
&lt;&#x2F;span&gt;&lt;span&gt;protocol=https
&lt;&#x2F;span&gt;&lt;span&gt;verbosity=1
&lt;&#x2F;span&gt;&lt;span&gt;trim_tree=0
&lt;&#x2F;span&gt;&lt;span&gt;extra_files=0
&lt;&#x2F;span&gt;&lt;span&gt;#repository_base=
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;[release]
&lt;&#x2F;span&gt;&lt;span&gt;branch=base&#x2F;releng&#x2F;12.2
&lt;&#x2F;span&gt;&lt;span&gt;target=&#x2F;usr&#x2F;src
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;[ports]
&lt;&#x2F;span&gt;&lt;span&gt;branch=ports&#x2F;head
&lt;&#x2F;span&gt;&lt;span&gt;target=&#x2F;usr&#x2F;ports
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;[stable]
&lt;&#x2F;span&gt;&lt;span&gt;branch=base&#x2F;stable&#x2F;12
&lt;&#x2F;span&gt;&lt;span&gt;target=&#x2F;usr&#x2F;src
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;[current]
&lt;&#x2F;span&gt;&lt;span&gt;branch=base&#x2F;head
&lt;&#x2F;span&gt;&lt;span&gt;target=&#x2F;usr&#x2F;src
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
</description>
      </item>
      <item>
          <title>Vanilla Javascript and Under Engineering</title>
          <pubDate>Thu, 29 Oct 2020 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/daily/vanilla-javascript-and-under-engineering/</link>
          <guid>https://www.gridbugs.org/daily/vanilla-javascript-and-under-engineering/</guid>
          <description xml:base="https://www.gridbugs.org/daily/vanilla-javascript-and-under-engineering/">&lt;p&gt;I enjoy the safety of typescript, and the ease with which one can
think about a UI made with react. Webpack lets me organize my source
and compiled code, and transpile the former into the latter, frontend
and backend, however I see fit.&lt;&#x2F;p&gt;
&lt;p&gt;But sometimes I just want to make something.&lt;&#x2F;p&gt;
&lt;p&gt;Earlier this week I spent a few hours over a couple of days making
a &lt;a href=&quot;https:&#x2F;&#x2F;gridbugs.github.io&#x2F;small-wolf&quot;&gt;3D renderer in the style of Wolfenstein 3D&lt;&#x2F;a&gt;.
It runs in the browser. The website has 2 files: an &lt;code&gt;index.html&lt;&#x2F;code&gt; referencing a file &lt;code&gt;wolf.js&lt;&#x2F;code&gt;.
You run the site with &lt;code&gt;python -m http.server&lt;&#x2F;code&gt; or whatever other static file webserver you like.
The code itself is written with the aim of writing it quickly and not overthinking anything.
Most of the state is in global mutable variables. No effort is made to abstract anything;
the world is an array of integers set to 0 for floor or 1 for wall and in lieu of a coordinate
class I just do the few 2D vector operations I need inline.&lt;&#x2F;p&gt;
&lt;p&gt;It’s certainly not the nicest code I’ve ever written, and this approach would be maddening on
a large project, but damn it was fun to make!&lt;&#x2F;p&gt;
&lt;p&gt;Here’s another screenshot:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;daily&#x2F;vanilla-javascript-and-under-engineering&#x2F;screenshot.png&quot; alt=&quot;screenshot.png&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Simple Wolfenstein-3D-esque renderer</title>
          <pubDate>Wed, 28 Oct 2020 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/daily/simple-wolfenstein-3d-esque-renderer/</link>
          <guid>https://www.gridbugs.org/daily/simple-wolfenstein-3d-esque-renderer/</guid>
          <description xml:base="https://www.gridbugs.org/daily/simple-wolfenstein-3d-esque-renderer/">&lt;p&gt;Just for fun I implemented a simple 3D renderer using the technique employed in Wolfenstein 3D.
The world is procedurally generated using the technique described
&lt;a href=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;cellular-automata-cave-generation&#x2F;&quot;&gt;here&lt;&#x2F;a&gt;.
The renderer works by finding the depth of walls for each row of pixels on the screen, and
drawing a vertically-centered vertical wall-coloured strip whose length is inversely proportional
to the wall depth at that point. Wall colour has brightness inversely proportional to the
square of the straight-line distance from the eye to the wall in the direction of the pixel row.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;daily&#x2F;simple-wolfenstein-3d-esque-renderer&#x2F;screenshot.png&quot; alt=&quot;screenshot.png&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Code: &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;gridbugs&#x2F;small-wolf&quot;&gt;https:&#x2F;&#x2F;github.com&#x2F;gridbugs&#x2F;small-wolf&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Demo: &lt;a href=&quot;https:&#x2F;&#x2F;gridbugs.github.io&#x2F;small-wolf&quot;&gt;https:&#x2F;&#x2F;gridbugs.github.io&#x2F;small-wolf&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Simple Wolfenstein-3D-esque renderer</title>
          <pubDate>Wed, 28 Oct 2020 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/simple-wolfenstein-3D-esque-renderer/</link>
          <guid>https://www.gridbugs.org/simple-wolfenstein-3D-esque-renderer/</guid>
          <description xml:base="https://www.gridbugs.org/simple-wolfenstein-3D-esque-renderer/">&lt;p&gt;Just for fun I implemented a simple 3D renderer using the technique employed in Wolfenstein 3D.
The world is procedurally generated using the technique described
&lt;a href=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;cellular-automata-cave-generation&#x2F;&quot;&gt;here&lt;&#x2F;a&gt;.
The renderer works by finding the depth of walls for each row of pixels on the screen, and
drawing a vertically-centered vertical wall-coloured strip whose length is inversely proportional
to the wall depth at that point. Wall colour has brightness inversely proportional to the
square of the straight-line distance from the eye to the wall in the direction of the pixel row.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;simple-wolfenstein-3D-esque-renderer&#x2F;screenshot.png&quot; alt=&quot;screenshot.png&quot; &#x2F;&gt;&lt;&#x2F;p&gt;</description>
      </item>
      <item>
          <title>Parsing and Generating GUIDs in GPT Header Fields</title>
          <pubDate>Tue, 27 Oct 2020 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/daily/parsing-and-generating-guids-in-gpt-header-fields/</link>
          <guid>https://www.gridbugs.org/daily/parsing-and-generating-guids-in-gpt-header-fields/</guid>
          <description xml:base="https://www.gridbugs.org/daily/parsing-and-generating-guids-in-gpt-header-fields/">&lt;p&gt;A while back I wrote about
&lt;a href=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;daily&#x2F;formatting-guids-in-gpt-header-fields&#x2F;&quot;&gt;the binary format of GUIDs in GPT headers&lt;&#x2F;a&gt;.
Based on some examples I reverse-engineered conversion between a 128-bit integer on disk, and a UUID’s
fields.&lt;&#x2F;p&gt;
&lt;p&gt;I’ve likely duplicated some logic from &lt;a href=&quot;https:&#x2F;&#x2F;crates.io&#x2F;crates&#x2F;winapi&quot;&gt;winapi&lt;&#x2F;a&gt;.
In the &lt;a href=&quot;https:&#x2F;&#x2F;crates.io&#x2F;crates&#x2F;uuid&quot;&gt;uuid&lt;&#x2F;a&gt; crate, a feature can be enabled to include code
for converting UUIDs into GUIDs (defined in winapi), but it only works when the target
is windows (a requirement of winapi).&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Cellular automata drawing prompt generator</title>
          <pubDate>Mon, 26 Oct 2020 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/daily/cellular-automata-drawing-prompt-generator/</link>
          <guid>https://www.gridbugs.org/daily/cellular-automata-drawing-prompt-generator/</guid>
          <description xml:base="https://www.gridbugs.org/daily/cellular-automata-drawing-prompt-generator/">&lt;p&gt;While working on a totally unrelated project, I accidentally implemented a procedural
drawing prompt generator. The technique used to generate the images is a simple modification
to Conway’s game of life. I’ve used this algorithm to generate caves for roguelikes in the
past. Here’s an old post that explains how it works: &lt;a href=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;cellular-automata-cave-generation&#x2F;&quot;&gt;Cellular Automata Cave Generation&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Here’s a link to the drawing prompt generator: &lt;a href=&quot;https:&#x2F;&#x2F;gridbugs.github.io&#x2F;drawing-prompt&#x2F;&quot;&gt;gridbugs.github.io&#x2F;drawing-prompt&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Repository of new words</title>
          <pubDate>Sun, 25 Oct 2020 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/daily/repository-of-new-words/</link>
          <guid>https://www.gridbugs.org/daily/repository-of-new-words/</guid>
          <description xml:base="https://www.gridbugs.org/daily/repository-of-new-words/">&lt;p&gt;I’ve started a git repo where I write down the definition of words that I encounter for the first time.
Almost every cryptic crossword I’ve solved (or attempted to solve!) has included a word which I’d not heard before,
and I’ve surely forgotten most of the new words I’ve discovered.
This will be an experiment to see if it helps me retain the words I learn from crosswords.
Repo is here: &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;gridbugs&#x2F;words&quot;&gt;github.com&#x2F;gridbugs&#x2F;words&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Boss fights in traditional roguelikes</title>
          <pubDate>Sat, 24 Oct 2020 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/boss-fights-in-traditional-roguelikes/</link>
          <guid>https://www.gridbugs.org/boss-fights-in-traditional-roguelikes/</guid>
          <description xml:base="https://www.gridbugs.org/boss-fights-in-traditional-roguelikes/">&lt;p&gt;Today I completed my first successful run of &lt;a href=&quot;https:&#x2F;&#x2F;jupiterhell.com&#x2F;&quot;&gt;Jupiter Hell&lt;&#x2F;a&gt; -
a traditional roguelike which is the spiritual successor to &lt;a href=&quot;https:&#x2F;&#x2F;drl.chaosforge.org&#x2F;&quot;&gt;DOOM the Roguelike&lt;&#x2F;a&gt;.
As a traditional roguelike, gameplay is turn-based, and levels are abstracted as 2D square grids of tiles.&lt;&#x2F;p&gt;
&lt;p&gt;Another property that tends to be true of roguelikes is that most turns are uninteresting - walking down a corridor,
or attacking a single standard enemy. There are meaningful &lt;em&gt;strategic&lt;&#x2F;em&gt; decisions to be made from time to time,
such as choosing whether to discard an item from your inventory to make room for a new item or deciding on a skill
to upgrade. Meaningful &lt;em&gt;tactical&lt;&#x2F;em&gt; decisions come about when a situation rapidly becomes dire (Do you stand your ground or flee
when surprised by a difficult enemy?) or the combination of enemies and terrain presents an opportunity (Which enemy do you
attack first? Is there time to run to cover? Should you use a rare&#x2F;expensive grenade? Should you trigger an explosive barrel?).&lt;&#x2F;p&gt;
&lt;p&gt;Jupiter Hell culminates with a boss fight fairly typical of DOOM-inspired games.
You fight a large enemy that periodically spawns normal enemies and then teleports away.
After a few such iterations my dual-pistol toting marine made short work of it.
As with the boss fight at the end of Doom the Roguelike, I found it to be an anti-climactic
conclusion to an otherwise solid game.&lt;&#x2F;p&gt;
&lt;p&gt;Upon further reflection, I’m not convinced that the roguelike genre supports boss fights at all.&lt;&#x2F;p&gt;</description>
      </item>
      <item>
          <title>Boss fights in traditional roguelikes</title>
          <pubDate>Sat, 24 Oct 2020 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/daily/boss-fights-in-traditional-roguelikes/</link>
          <guid>https://www.gridbugs.org/daily/boss-fights-in-traditional-roguelikes/</guid>
          <description xml:base="https://www.gridbugs.org/daily/boss-fights-in-traditional-roguelikes/">&lt;p&gt;Today I completed my first successful run of &lt;a href=&quot;https:&#x2F;&#x2F;jupiterhell.com&#x2F;&quot;&gt;Jupiter Hell&lt;&#x2F;a&gt; -
a traditional roguelike which is the spiritual successor to &lt;a href=&quot;https:&#x2F;&#x2F;drl.chaosforge.org&#x2F;&quot;&gt;DOOM the Roguelike&lt;&#x2F;a&gt;.
As a traditional roguelike, gameplay is turn-based, and levels are abstracted as 2D square grids of tiles.&lt;&#x2F;p&gt;
&lt;p&gt;Another property that tends to be true of roguelikes is that most turns are uninteresting - walking down a corridor,
or attacking a single standard enemy. There are meaningful &lt;em&gt;strategic&lt;&#x2F;em&gt; decisions to be made from time to time,
such as choosing whether to discard an item from your inventory to make room for a new item or deciding on a skill
to upgrade. Meaningful &lt;em&gt;tactical&lt;&#x2F;em&gt; decisions come about when a situation rapidly becomes dire (Do you stand your ground or flee
when surprised by a difficult enemy?) or the combination of enemies and terrain presents an opportunity (Which enemy do you
attack first? Is there time to run to cover? Should you use a rare&#x2F;expensive grenade? Should you trigger an explosive barrel?).&lt;&#x2F;p&gt;
&lt;p&gt;Jupiter Hell culminates with a boss fight fairly typical of DOOM-inspired games.
You fight a large enemy that periodically spawns normal enemies and then teleports away.
After a few such iterations my dual-pistol toting marine made short work of it.
As with the boss fight at the end of Doom the Roguelike, I found it to be an anti-climactic
conclusion to an otherwise solid game.&lt;&#x2F;p&gt;
&lt;p&gt;Upon further reflection, I’m not convinced that the roguelike genre supports boss fights at all.&lt;&#x2F;p&gt;
&lt;p&gt;In real-time games, or some non-roguelike turn-based games, a typical boss fight
involves the player fighting a single tougher-than-usual enemy in a closed-off arena.
Gameplay during a boss fight should resemble standard gameplay that has been enhanced, or purified in some way.
The boss fight becomes a way for players to prove to the games that they have mastered some aspects of its mechanics.&lt;&#x2F;p&gt;
&lt;p&gt;To defeat Smough and Ornstein
in Dark Souls you must demonstrate your ability to patiently wait for the right time to strike,
and time attacks, dodges, and blocks perfectly, which is what melee combat in Dark Souls is all about.
The Icon of Sin at the end of Doom II (or Doom Eternal) forces you to manage an endless horde of
hell spawn while also shooting the boss itself until it’s dead, which is the main mechanic of the
game, only more-so.
In Darkest Dungeon - a turned based game with some roguelike properties, the combat system is entirely based on
abilities, with enough variety and (anti)synergy with one another; unlike traditional roguelikes, &lt;em&gt;every&lt;&#x2F;em&gt; turn
feels like a meaningful decision.
Because of the richness of combat, all 10 or so boss fights feel fresh (except for the part where they re-use
the same boss 3 times for each area - but that’s a different issue!).&lt;&#x2F;p&gt;
&lt;p&gt;Which brings us back to traditional roguelikes. The richness of combat in the genre comes from the interactions
between groups of enemies, the terrain, and the player. In a boss arena, where there is only a single enemy (plus
its summons, perhaps), the number of interesting interactions is low, compared to normal, non-boss gameplay.
Boss fights feel repetitive and boring when you win, and an unfair skill-check when you loose.
Rarely does a decision made by the player during a boss fight meaningfully affect its outcome.
Gameplay during a boss fight is not just an amplified version of standard play, but instead a detraction from it.&lt;&#x2F;p&gt;
&lt;p&gt;So how do you conclude your roguelike? Originally, (in Rogue, say) the hero would reach the bottom of the dungeon, and retrieve
an item (traditionally an amulet), and then make it back to the surface, possibly pursued by the item’s
guardians. In their flight, the player may still need to fight remnant (or perhaps newly-spawned) enemies on
floors as they ascend, but now they might be under time pressure due to their pursuers, or item pressure as
the floors were already looted by the player on their way down. The game’s culmination is the same experience
as normal gameplay, only enhanced in some way.&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>The latest rust release broke backtraces on FreeBSD</title>
          <pubDate>Fri, 23 Oct 2020 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/daily/the-latest-rust-release-broke-backtraces-on-freebsd/</link>
          <guid>https://www.gridbugs.org/daily/the-latest-rust-release-broke-backtraces-on-freebsd/</guid>
          <description xml:base="https://www.gridbugs.org/daily/the-latest-rust-release-broke-backtraces-on-freebsd/">&lt;p&gt;Rust 1.47 broke backtraces on FreeBSD. Backtraces are now handled by &lt;a href=&quot;https:&#x2F;&#x2F;crates.io&#x2F;crates&#x2F;gimli&quot;&gt;gimli&lt;&#x2F;a&gt;
which does not support meaningful backtraces on FreeBSD at the time of writing. Backtraces now look like:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;stack backtrace:
&lt;&#x2F;span&gt;&lt;span&gt;   0:          0x11298b1 - &amp;lt;unknown&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;   1:          0x1141d60 - &amp;lt;unknown&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;   2:          0x11277df - &amp;lt;unknown&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;   3:          0x112b59d - &amp;lt;unknown&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;   4:          0x112b24c - &amp;lt;unknown&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;   5:          0x112bc45 - &amp;lt;unknown&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;   6:          0x112b80d - &amp;lt;unknown&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;   7:          0x1129d70 - &amp;lt;unknown&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;   8:          0x112b7cc - &amp;lt;unknown&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;   9:          0x1141040 - &amp;lt;unknown&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;  10:          0x11437f7 - &amp;lt;unknown&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;  11:          0x10a0864 - &amp;lt;unknown&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;  12:          0x1096c59 - &amp;lt;unknown&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;  13:          0x1096095 - &amp;lt;unknown&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;  14:          0x108351d - &amp;lt;unknown&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;  15:          0x1073e66 - &amp;lt;unknown&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;  16:          0x108af2e - &amp;lt;unknown&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;  17:          0x107bad1 - &amp;lt;unknown&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;  18:          0x106a3c4 - &amp;lt;unknown&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;  19:          0x112c0ae - &amp;lt;unknown&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;  20:          0x106a3a2 - &amp;lt;unknown&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;  21:          0x1073f0b - &amp;lt;unknown&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;  22:          0x106a10b - &amp;lt;unknown&amp;gt;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;On rust 1.46, they looked like this:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;stack backtrace:
&lt;&#x2F;span&gt;&lt;span&gt;   0: backtrace::backtrace::libunwind::trace
&lt;&#x2F;span&gt;&lt;span&gt;             at &#x2F;cargo&#x2F;registry&#x2F;src&#x2F;github.com-1ecc6299db9ec823&#x2F;backtrace-0.3.46&#x2F;src&#x2F;backtrace&#x2F;libunwind.rs:86
&lt;&#x2F;span&gt;&lt;span&gt;   1: backtrace::backtrace::trace_unsynchronized
&lt;&#x2F;span&gt;&lt;span&gt;             at &#x2F;cargo&#x2F;registry&#x2F;src&#x2F;github.com-1ecc6299db9ec823&#x2F;backtrace-0.3.46&#x2F;src&#x2F;backtrace&#x2F;mod.rs:66
&lt;&#x2F;span&gt;&lt;span&gt;   2: std::sys_common::backtrace::_print_fmt
&lt;&#x2F;span&gt;&lt;span&gt;             at src&#x2F;libstd&#x2F;sys_common&#x2F;backtrace.rs:78
&lt;&#x2F;span&gt;&lt;span&gt;   3: &amp;lt;std::sys_common::backtrace::_print::DisplayBacktrace as core::fmt::Display&amp;gt;::fmt
&lt;&#x2F;span&gt;&lt;span&gt;             at src&#x2F;libstd&#x2F;sys_common&#x2F;backtrace.rs:59
&lt;&#x2F;span&gt;&lt;span&gt;   4: core::fmt::write
&lt;&#x2F;span&gt;&lt;span&gt;             at src&#x2F;libcore&#x2F;fmt&#x2F;mod.rs:1076
&lt;&#x2F;span&gt;&lt;span&gt;   5: std::io::Write::write_fmt
&lt;&#x2F;span&gt;&lt;span&gt;             at src&#x2F;libstd&#x2F;io&#x2F;mod.rs:1537
&lt;&#x2F;span&gt;&lt;span&gt;   6: std::sys_common::backtrace::_print
&lt;&#x2F;span&gt;&lt;span&gt;             at src&#x2F;libstd&#x2F;sys_common&#x2F;backtrace.rs:62
&lt;&#x2F;span&gt;&lt;span&gt;   7: std::sys_common::backtrace::print
&lt;&#x2F;span&gt;&lt;span&gt;             at src&#x2F;libstd&#x2F;sys_common&#x2F;backtrace.rs:49
&lt;&#x2F;span&gt;&lt;span&gt;   8: std::panicking::default_hook::{{closure}}
&lt;&#x2F;span&gt;&lt;span&gt;             at src&#x2F;libstd&#x2F;panicking.rs:198
&lt;&#x2F;span&gt;&lt;span&gt;   9: std::panicking::default_hook
&lt;&#x2F;span&gt;&lt;span&gt;             at src&#x2F;libstd&#x2F;panicking.rs:217
&lt;&#x2F;span&gt;&lt;span&gt;  10: std::panicking::rust_panic_with_hook
&lt;&#x2F;span&gt;&lt;span&gt;             at src&#x2F;libstd&#x2F;panicking.rs:526
&lt;&#x2F;span&gt;&lt;span&gt;  11: rust_begin_unwind
&lt;&#x2F;span&gt;&lt;span&gt;             at src&#x2F;libstd&#x2F;panicking.rs:437
&lt;&#x2F;span&gt;&lt;span&gt;  12: core::panicking::panic_fmt
&lt;&#x2F;span&gt;&lt;span&gt;             at src&#x2F;libcore&#x2F;panicking.rs:85
&lt;&#x2F;span&gt;&lt;span&gt;  13: core::slice::&amp;lt;impl [T]&amp;gt;::copy_from_slice
&lt;&#x2F;span&gt;&lt;span&gt;             at &#x2F;rustc&#x2F;04488afe34512aa4c33566eb16d8c912a3ae04f9&#x2F;src&#x2F;libcore&#x2F;macros&#x2F;mod.rs:16
&lt;&#x2F;span&gt;&lt;span&gt;  14: mini_gpt::GptHeader::crc32_from_logical_block
&lt;&#x2F;span&gt;&lt;span&gt;             at mini-gpt&#x2F;src&#x2F;lib.rs:166
&lt;&#x2F;span&gt;&lt;span&gt;  15: mini_gpt::GptHeader::parse
&lt;&#x2F;span&gt;&lt;span&gt;             at mini-gpt&#x2F;src&#x2F;lib.rs:124
&lt;&#x2F;span&gt;&lt;span&gt;  16: mini_gpt::write_header
&lt;&#x2F;span&gt;&lt;span&gt;             at .&#x2F;mini-gpt&#x2F;src&#x2F;lib.rs:614
&lt;&#x2F;span&gt;&lt;span&gt;  17: gpt_fat_disk_image_create::main
&lt;&#x2F;span&gt;&lt;span&gt;             at tools&#x2F;src&#x2F;create.rs:55
&lt;&#x2F;span&gt;&lt;span&gt;  18: std::rt::lang_start::{{closure}}
&lt;&#x2F;span&gt;&lt;span&gt;             at &#x2F;rustc&#x2F;04488afe34512aa4c33566eb16d8c912a3ae04f9&#x2F;src&#x2F;libstd&#x2F;rt.rs:67
&lt;&#x2F;span&gt;&lt;span&gt;  19: std::rt::lang_start_internal::{{closure}}
&lt;&#x2F;span&gt;&lt;span&gt;             at src&#x2F;libstd&#x2F;rt.rs:52
&lt;&#x2F;span&gt;&lt;span&gt;  20: std::panicking::try::do_call
&lt;&#x2F;span&gt;&lt;span&gt;             at src&#x2F;libstd&#x2F;panicking.rs:348
&lt;&#x2F;span&gt;&lt;span&gt;  21: std::panicking::try
&lt;&#x2F;span&gt;&lt;span&gt;             at src&#x2F;libstd&#x2F;panicking.rs:325
&lt;&#x2F;span&gt;&lt;span&gt;  22: std::panic::catch_unwind
&lt;&#x2F;span&gt;&lt;span&gt;             at src&#x2F;libstd&#x2F;panic.rs:394
&lt;&#x2F;span&gt;&lt;span&gt;  23: std::rt::lang_start_internal
&lt;&#x2F;span&gt;&lt;span&gt;             at src&#x2F;libstd&#x2F;rt.rs:51
&lt;&#x2F;span&gt;&lt;span&gt;  24: std::rt::lang_start
&lt;&#x2F;span&gt;&lt;span&gt;             at &#x2F;rustc&#x2F;04488afe34512aa4c33566eb16d8c912a3ae04f9&#x2F;src&#x2F;libstd&#x2F;rt.rs:67
&lt;&#x2F;span&gt;&lt;span&gt;  25: main
&lt;&#x2F;span&gt;&lt;span&gt;  26: _start
&lt;&#x2F;span&gt;&lt;span&gt;             at &#x2F;usr&#x2F;src&#x2F;lib&#x2F;csu&#x2F;amd64&#x2F;crt1.c:76
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This was a regression in the rust compiler and not a problem with FreeBSD as I
&lt;a href=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;daily&#x2F;rust-stacktraces-are-unknown-on-freebsd&#x2F;&quot;&gt;previously thought&lt;&#x2F;a&gt;,
which is a relief. FreeBSD is a &lt;a href=&quot;https:&#x2F;&#x2F;doc.rust-lang.org&#x2F;nightly&#x2F;rustc&#x2F;platform-support.html#tier-2&quot;&gt;tier 2&lt;&#x2F;a&gt;
rust platform, so tests don’t necessarily run, and breakages like this one aren’t
the end of the world, though this is the first real problem I’ve run into using rust
on FreeBSD.&lt;&#x2F;p&gt;
&lt;p&gt;The temporary solution is to switch back to rust 1.46. This meant changing back some parts
of my GPT FAT disk tools that were taking advantage of the
&lt;a href=&quot;https:&#x2F;&#x2F;blog.rust-lang.org&#x2F;2020&#x2F;10&#x2F;08&#x2F;Rust-1.47.html#traits-on-larger-arrays&quot;&gt;const generic teaser&lt;&#x2F;a&gt;
included in 1.47.&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Raidz setup</title>
          <pubDate>Thu, 22 Oct 2020 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/daily/raidz-setup/</link>
          <guid>https://www.gridbugs.org/daily/raidz-setup/</guid>
          <description xml:base="https://www.gridbugs.org/daily/raidz-setup/">&lt;p&gt;Tonight I set up a raidz zpool on 3 4TB external hard drives.
This is exactly the &lt;a href=&quot;https:&#x2F;&#x2F;www.freebsd.org&#x2F;doc&#x2F;handbook&#x2F;zfs-quickstart.html&quot;&gt;ZFS example in the FreeBSD handbook&lt;&#x2F;a&gt;,
with the minor variation that one of the disks is &lt;em&gt;slightly&lt;&#x2F;em&gt; smaller
than the other 2, so &lt;code&gt;-f&lt;&#x2F;code&gt; must be specified when creating the zpool.&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;# zpool create storage raidz da1 da2 da3
&lt;&#x2F;span&gt;&lt;span&gt;invalid vdev specification
&lt;&#x2F;span&gt;&lt;span&gt;use &amp;#39;-f&amp;#39; to override the following errors:
&lt;&#x2F;span&gt;&lt;span&gt;raidz contains devices of different sizes
&lt;&#x2F;span&gt;&lt;span&gt;# zpool create -f storage raidz da1 da2 da3
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This will be used in a media center. Create a new filesystem for storing media:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;# zfs create storage&#x2F;media
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;I’m trying out the compression feature of ZFS. I doubt it will do much in this case as
all the files I’ll be storing in this filesystem will be already compressed, but I’m
interested to see how much of a difference it makes anyway.&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;# zfs set compression=on storage&#x2F;media
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The 7TB below (instead of 8TB) is due to the unusual way hard drive sizes are specified.
4TB is more like 3.5TB. One disk size worth of storage is taken up with redundancy, which
is why the total size of the pool is twice that of a single disk (rather than 3 times).
Were one of the disks to fail, no data would be lost and the pool would continue to function
as normal.&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;# df -h storage&#x2F;media
&lt;&#x2F;span&gt;&lt;span&gt;Filesystem       Size    Used   Avail Capacity  Mounted on
&lt;&#x2F;span&gt;&lt;span&gt;storage&#x2F;media    7.0T     92G    6.9T     1%    &#x2F;storage&#x2F;media
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The zpool hierarchy showing the 3 disks:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;# zpool status storage
&lt;&#x2F;span&gt;&lt;span&gt;  pool: storage
&lt;&#x2F;span&gt;&lt;span&gt; state: ONLINE
&lt;&#x2F;span&gt;&lt;span&gt;  scan: none requested
&lt;&#x2F;span&gt;&lt;span&gt;config:
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;        NAME        STATE     READ WRITE CKSUM
&lt;&#x2F;span&gt;&lt;span&gt;        storage     ONLINE       0     0     0
&lt;&#x2F;span&gt;&lt;span&gt;          raidz1-0  ONLINE       0     0     0
&lt;&#x2F;span&gt;&lt;span&gt;            da1     ONLINE       0     0     0
&lt;&#x2F;span&gt;&lt;span&gt;            da2     ONLINE       0     0     0
&lt;&#x2F;span&gt;&lt;span&gt;            da3     ONLINE       0     0     0
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;errors: No known data errors
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
</description>
      </item>
      <item>
          <title>Rust stacktraces are &lt;unknown&gt; on FreeBSD</title>
          <pubDate>Wed, 21 Oct 2020 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/daily/rust-stacktraces-are-unknown-on-freebsd/</link>
          <guid>https://www.gridbugs.org/daily/rust-stacktraces-are-unknown-on-freebsd/</guid>
          <description xml:base="https://www.gridbugs.org/daily/rust-stacktraces-are-unknown-on-freebsd/">&lt;p&gt;Tonight I ran into an issue where when a rust program panics on my FreeBSD
laptop, the stacktrace doesn’t contain code locations, and instead shows
&lt;code&gt;&amp;lt;unknown&amp;gt;&lt;&#x2F;code&gt; on every line:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;$ RUST_BACKTRACE=full cargo run --bin gpt-fat-disk-image-create -- -l README.md -d &#x2F;foo&#x2F;bar&#x2F;baz.md -o &#x2F;tmp&#x2F;a
&lt;&#x2F;span&gt;&lt;span&gt;    Finished dev [unoptimized + debuginfo] target(s) in 0.06s
&lt;&#x2F;span&gt;&lt;span&gt;     Running `target&#x2F;debug&#x2F;gpt-fat-disk-image-create -l README.md -d &#x2F;foo&#x2F;bar&#x2F;baz.md -o &#x2F;tmp&#x2F;a`
&lt;&#x2F;span&gt;&lt;span&gt;thread &amp;#39;main&amp;#39; panicked at &amp;#39;source slice length (92) does not match destination slice length (512)&amp;#39;, &#x2F;rustc&#x2F;31530e5d132ebcc3654baf2e5460599681520af0&#x2F;library&#x2F;core&#x2F;src&#x2F;slice&#x2F;mod.rs:2673:13
&lt;&#x2F;span&gt;&lt;span&gt;stack backtrace:
&lt;&#x2F;span&gt;&lt;span&gt;   0:          0x11298b1 - &amp;lt;unknown&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;   1:          0x1141d60 - &amp;lt;unknown&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;   2:          0x11277df - &amp;lt;unknown&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;   3:          0x112b59d - &amp;lt;unknown&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;   4:          0x112b24c - &amp;lt;unknown&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;   5:          0x112bc45 - &amp;lt;unknown&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;   6:          0x112b80d - &amp;lt;unknown&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;   7:          0x1129d70 - &amp;lt;unknown&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;   8:          0x112b7cc - &amp;lt;unknown&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;   9:          0x1141040 - &amp;lt;unknown&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;  10:          0x11437f7 - &amp;lt;unknown&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;  11:          0x10a0864 - &amp;lt;unknown&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;  12:          0x1096c59 - &amp;lt;unknown&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;  13:          0x1096095 - &amp;lt;unknown&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;  14:          0x108351d - &amp;lt;unknown&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;  15:          0x1073e66 - &amp;lt;unknown&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;  16:          0x108af2e - &amp;lt;unknown&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;  17:          0x107bad1 - &amp;lt;unknown&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;  18:          0x106a3c4 - &amp;lt;unknown&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;  19:          0x112c0ae - &amp;lt;unknown&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;  20:          0x106a3a2 - &amp;lt;unknown&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;  21:          0x1073f0b - &amp;lt;unknown&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;  22:          0x106a10b - &amp;lt;unknown&amp;gt;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Sounds at least related to a
&lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;rust-lang&#x2F;rust&#x2F;issues&#x2F;54434&quot;&gt;known issue&lt;&#x2F;a&gt;, but that issue is already
closed and not necessarily the same problem so I raised a new one.
It’s not clear whether the problem lies with rust or FreeBSD (or elsewhere).&lt;&#x2F;p&gt;
&lt;p&gt;But this is a major hindrance to my productivity!
Rust development is the main thing I use this computer for.
There are a couple of Linux distros I’ve been meaning to check out, namely &lt;a href=&quot;https:&#x2F;&#x2F;voidlinux.org&#x2F;&quot;&gt;void&lt;&#x2F;a&gt;
and &lt;a href=&quot;https:&#x2F;&#x2F;crux.nu&quot;&gt;crux&lt;&#x2F;a&gt;, and I can always go crawling back to archlinux if they don’t work out.&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Train Game</title>
          <pubDate>Tue, 20 Oct 2020 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/daily/train-game/</link>
          <guid>https://www.gridbugs.org/daily/train-game/</guid>
          <description xml:base="https://www.gridbugs.org/daily/train-game/">&lt;p&gt;Digging through an old hard drive I found a game I forgot I made.
This was in a time before every whimsical idea I had went into a
repo on github, and instead I would store repositories on a private
server. That server is gone now, and all that remains is the local
checkouts of projects on an almost forgotten hard disk.&lt;&#x2F;p&gt;
&lt;p&gt;So I salvaged what I could find, moving all the private repos onto
github, and added the &lt;a href=&quot;https:&#x2F;&#x2F;games.gridbugs.org&#x2F;train&quot;&gt;train game&lt;&#x2F;a&gt;
to games.gridbugs.org. It was my entry into the 2015 JS1K competition,
but it looks like I had to simplify the graphic of the train to get
the game (including graphics) to fit in under 1K.&lt;&#x2F;p&gt;
&lt;p&gt;JS1K page for the train game: &lt;a href=&quot;https:&#x2F;&#x2F;js1k.com&#x2F;2015-hypetrain&#x2F;details&#x2F;2309&quot;&gt;https:&#x2F;&#x2F;js1k.com&#x2F;2015-hypetrain&#x2F;details&#x2F;2309&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Fixing zpool after FreeBSD fresh install on ZFS mirror</title>
          <pubDate>Mon, 19 Oct 2020 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/daily/fixing-zpool-after-freebsd-fresh-install-on-zfs-mirror/</link>
          <guid>https://www.gridbugs.org/daily/fixing-zpool-after-freebsd-fresh-install-on-zfs-mirror/</guid>
          <description xml:base="https://www.gridbugs.org/daily/fixing-zpool-after-freebsd-fresh-install-on-zfs-mirror/">&lt;p&gt;I bought some new SSDs to install FreeBSD on a media server I’m setting up.
During install, I elected to install the OS on a ZFS mirror, using a pair
of SSDs. Upon booting up, I checked the zpool status:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;# zpool status
&lt;&#x2F;span&gt;&lt;span&gt;  pool: zroot
&lt;&#x2F;span&gt;&lt;span&gt; state: ONLINE
&lt;&#x2F;span&gt;&lt;span&gt;status: One or more devices could not be used because the label is missing or
&lt;&#x2F;span&gt;&lt;span&gt;        invalid.  Sufficient replicas exist for the pool to continue
&lt;&#x2F;span&gt;&lt;span&gt;        functioning in a degraded state.
&lt;&#x2F;span&gt;&lt;span&gt;action: Replace the device using &amp;#39;zpool replace&amp;#39;.
&lt;&#x2F;span&gt;&lt;span&gt;   see: http:&#x2F;&#x2F;illumos.org&#x2F;msg&#x2F;ZFS-8000-4J
&lt;&#x2F;span&gt;&lt;span&gt;  scan: scrub repaired 0 in 0 days 00:00:09 with 0 errors on Mon Oct 19 20:13:49 2020
&lt;&#x2F;span&gt;&lt;span&gt;config:
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;        NAME                    STATE     READ WRITE CKSUM
&lt;&#x2F;span&gt;&lt;span&gt;        zroot                   ONLINE       0     0     0
&lt;&#x2F;span&gt;&lt;span&gt;          mirror-0              ONLINE       0     0     0
&lt;&#x2F;span&gt;&lt;span&gt;            ada0p4              ONLINE       0     0     0
&lt;&#x2F;span&gt;&lt;span&gt;            213823451083858949  UNAVAIL      0     0     0  was &#x2F;dev&#x2F;ada1p4
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;errors: No known data errors
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Strange. Stranger still:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;# zpool list
&lt;&#x2F;span&gt;&lt;span&gt;NAME    SIZE  ALLOC   FREE  CKPOINT  EXPANDSZ   FRAG    CAP  DEDUP  HEALTH  ALTROOT
&lt;&#x2F;span&gt;&lt;span&gt;zroot   230G  2.60G   227G        -         -     0%     1%  1.00x  ONLINE  -
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Ignoring that apparent inconsistency…&lt;&#x2F;p&gt;
&lt;p&gt;First I tried taking the disk offline, then bringing it back oniline:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;# zpool offline zroot ada1p4
&lt;&#x2F;span&gt;&lt;span&gt;# zpool online zroot ada1p4
&lt;&#x2F;span&gt;&lt;span&gt;warning: device &amp;#39;ada1p4&amp;#39; onlined, but remains in faulted state
&lt;&#x2F;span&gt;&lt;span&gt;use &amp;#39;zpool replace&amp;#39; to replace devices that are no longer present
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Then according to the instruction, I tried &lt;code&gt;replace&lt;&#x2F;code&gt;-ing the drive:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;# zpool replace zroot 213823451083858949 &#x2F;dev&#x2F;ada1p4
&lt;&#x2F;span&gt;&lt;span&gt;invalid vdev specification
&lt;&#x2F;span&gt;&lt;span&gt;use &amp;#39;-f&amp;#39; to override the following errors:
&lt;&#x2F;span&gt;&lt;span&gt;&#x2F;dev&#x2F;ada1p4 is part of active pool &amp;#39;zroot&amp;#39;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This didn’t help clarify the situation. Next I tried detaching and re-attaching the disk from the pool:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;# zpool detach zroot 213823451083858949
&lt;&#x2F;span&gt;&lt;span&gt;# zpool status
&lt;&#x2F;span&gt;&lt;span&gt;  pool: zroot
&lt;&#x2F;span&gt;&lt;span&gt; state: ONLINE
&lt;&#x2F;span&gt;&lt;span&gt;  scan: scrub repaired 0 in 0 days 00:00:11 with 0 errors on Mon Oct 19 21:06:06 2020
&lt;&#x2F;span&gt;&lt;span&gt;config:
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;        NAME        STATE     READ WRITE CKSUM
&lt;&#x2F;span&gt;&lt;span&gt;        zroot       ONLINE       0     0     0
&lt;&#x2F;span&gt;&lt;span&gt;          ada0p4    ONLINE       0     0     0
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;errors: No known data errors
&lt;&#x2F;span&gt;&lt;span&gt;# zpool attach zroot ada0p4 &#x2F;dev&#x2F;ada1
&lt;&#x2F;span&gt;&lt;span&gt;cannot attach &#x2F;dev&#x2F;ada1 to ada0p4: no such pool or dataset
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;I wish that last error message clarified which out of &lt;code&gt;zroot&lt;&#x2F;code&gt;, &lt;code&gt;&#x2F;dev&#x2F;ada1&lt;&#x2F;code&gt; and &lt;code&gt;ada0p4&lt;&#x2F;code&gt; it was referring
to by &lt;code&gt;no such pool or dataset&lt;&#x2F;code&gt;. Changing either argument to an obviously garbage value produced a
different error message.&lt;&#x2F;p&gt;
&lt;p&gt;Eventually I found a &lt;a href=&quot;https:&#x2F;&#x2F;dan.langille.org&#x2F;2019&#x2F;10&#x2F;15&#x2F;creating-a-mirror-from-your-zroot&#x2F;&quot;&gt;blog post&lt;&#x2F;a&gt;
by someone trying to do something similar to me, and a &lt;a href=&quot;https:&#x2F;&#x2F;forums.freebsd.org&#x2F;threads&#x2F;zpool-attach-no-such-pool-or-dataset.68292&#x2F;#post-407102&quot;&gt;forum post&lt;&#x2F;a&gt;
that suggested “using the full path (&#x2F;dev&#x2F;gpt&#x2F;&amp;lt;label&amp;gt;)” which worked:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;# zpool attach zroot ada0p4 &#x2F;dev&#x2F;gpt&#x2F;zfs1
&lt;&#x2F;span&gt;&lt;span&gt;Make sure to wait until resilver is done before rebooting.
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;If you boot from pool &amp;#39;zroot&amp;#39;, you may need to update
&lt;&#x2F;span&gt;&lt;span&gt;boot code on newly attached disk &amp;#39;&#x2F;dev&#x2F;gpt&#x2F;zfs1&amp;#39;.
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;Assuming you use GPT partitioning and &amp;#39;da0&amp;#39; is your new boot disk
&lt;&#x2F;span&gt;&lt;span&gt;you may use the following command:
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;        gpart bootcode -b &#x2F;boot&#x2F;pmbr -p &#x2F;boot&#x2F;gptzfsboot -i 1 da0
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;And for good measure:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;# gpart bootcode -b &#x2F;boot&#x2F;pmbr -p &#x2F;boot&#x2F;gptzfsboot -i 1 ada1
&lt;&#x2F;span&gt;&lt;span&gt;partcode written to ada1p1
&lt;&#x2F;span&gt;&lt;span&gt;bootcode written to ada1
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;And now things look ok:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;# zpool status
&lt;&#x2F;span&gt;&lt;span&gt;  pool: zroot
&lt;&#x2F;span&gt;&lt;span&gt; state: ONLINE
&lt;&#x2F;span&gt;&lt;span&gt;  scan: resilvered 2.60G in 0 days 00:00:12 with 0 errors on Mon Oct 19 21:28:10 2020
&lt;&#x2F;span&gt;&lt;span&gt;config:
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;        NAME          STATE     READ WRITE CKSUM
&lt;&#x2F;span&gt;&lt;span&gt;        zroot         ONLINE       0     0     0
&lt;&#x2F;span&gt;&lt;span&gt;          mirror-0    ONLINE       0     0     0
&lt;&#x2F;span&gt;&lt;span&gt;            ada0p4    ONLINE       0     0     0
&lt;&#x2F;span&gt;&lt;span&gt;            gpt&#x2F;zfs1  ONLINE       0     0     0
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;errors: No known data errors
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
</description>
      </item>
      <item>
          <title>New URL and title formats for daily posts</title>
          <pubDate>Sun, 18 Oct 2020 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/daily/new-url-and-title-formats-for-daily-posts/</link>
          <guid>https://www.gridbugs.org/daily/new-url-and-title-formats-for-daily-posts/</guid>
          <description xml:base="https://www.gridbugs.org/daily/new-url-and-title-formats-for-daily-posts/">&lt;p&gt;Today I updated the permalinks for these daily posts to be based on title of the post rather than the date.
The purpose is to make the URLs more meaningful.
When I write a post, I populate a new field which determines the permalink URL, and if
I forget it defaults to the date of the post.&lt;&#x2F;p&gt;
&lt;p&gt;I also removed the date from the heading and title of the pages that display the posts, displaying it under
the heading as I do for regular posts.&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Time to start using language managers?</title>
          <pubDate>Sat, 17 Oct 2020 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/daily/time-to-start-using-language-managers/</link>
          <guid>https://www.gridbugs.org/daily/time-to-start-using-language-managers/</guid>
          <description xml:base="https://www.gridbugs.org/daily/time-to-start-using-language-managers/">&lt;p&gt;A few months ago I &lt;a href=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;daily&#x2F;time-to-stop-using-language-managers&#x2F;&quot;&gt;swore off the nodejs and ruby version managers&lt;&#x2F;a&gt;,
claiming that the language ports should suffice. This was spurred by a bad experience trying to
build nodejs from source on openbsd, when I spent an evening hacking the nodejs source to get it
to build before it dawned on me that I was repeating the work of the port maintainers.&lt;&#x2F;p&gt;
&lt;p&gt;Tonight I installed the ruby version manager again because the only version of ruby and gem I could
find in the FreeBSD ports collection was 2.6. Ports for later versions of the ruby interpreter exist,
but not the corresponding version of the gem package manager.&lt;&#x2F;p&gt;
&lt;p&gt;Several months ago when I renounced my use of version managers, I was suspicious of RVM in
particular because it was using &lt;code&gt;sudo&lt;&#x2F;code&gt; to install dependencies as root by default when installing
rubies. This specific realization lead to me uninstalling &lt;code&gt;sudo&lt;&#x2F;code&gt; outright. Frustratingly, there
doesn’t seem to be a way to get a list of the dependencies I need so I can install them manually.
No by default I get a fairly unhelpful error when trying to install ruby:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;$ rvm install ruby-3 --disable-binary
&lt;&#x2F;span&gt;&lt;span&gt;Checking requirements for qR.
&lt;&#x2F;span&gt;&lt;span&gt;Requirements support for qR is not implemented yet,
&lt;&#x2F;span&gt;&lt;span&gt;report a bug here =&amp;gt; https:&#x2F;&#x2F;github.com&#x2F;rvm&#x2F;rvm&#x2F;issues
&lt;&#x2F;span&gt;&lt;span&gt;Requirements installation failed with status: 1.
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;(&lt;code&gt;--disable-binary&lt;&#x2F;code&gt; forces it to build ruby from source, which is necessary as there are no
binary distributions for FreeBSD.)&lt;&#x2F;p&gt;
&lt;p&gt;Pass &lt;code&gt;--debug&lt;&#x2F;code&gt; for extra verbosity:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;$ rvm install ruby-3 --disable-binary --debug
&lt;&#x2F;span&gt;&lt;span&gt;Warning: No &amp;#39;sudo&amp;#39; found.
&lt;&#x2F;span&gt;&lt;span&gt;Warning: No &amp;#39;sudo&amp;#39; found.
&lt;&#x2F;span&gt;&lt;span&gt;ruby-3.0.0-preview1 - install
&lt;&#x2F;span&gt;&lt;span&gt;ruby-3.0.0-preview1 - #already removed src&#x2F;ruby-3.0.0-preview1
&lt;&#x2F;span&gt;&lt;span&gt;ruby-3.0.0-preview1 - #already removed rubies&#x2F;ruby-3.0.0-preview1
&lt;&#x2F;span&gt;&lt;span&gt;Free disk space 194019MB, required 440MB.
&lt;&#x2F;span&gt;&lt;span&gt;__rvm_setup_compile_environment_setup ruby-3.0.0-preview1
&lt;&#x2F;span&gt;&lt;span&gt;rvm_autolibs_flag=fail
&lt;&#x2F;span&gt;&lt;span&gt;__rvm_setup_compile_environment_movable_early ruby-3.0.0-preview1
&lt;&#x2F;span&gt;&lt;span&gt;__rvm_setup_compile_environment_system_early ruby-3.0.0-preview1
&lt;&#x2F;span&gt;&lt;span&gt;__rvm_setup_compile_environment_requirements ruby-3.0.0-preview1
&lt;&#x2F;span&gt;&lt;span&gt;Checking requirements for qR.
&lt;&#x2F;span&gt;&lt;span&gt;Requirements support for qR is not implemented yet,
&lt;&#x2F;span&gt;&lt;span&gt;report a bug here =&amp;gt; https:&#x2F;&#x2F;github.com&#x2F;rvm&#x2F;rvm&#x2F;issues
&lt;&#x2F;span&gt;&lt;span&gt;Requirements installation failed with status: 1.
&lt;&#x2F;span&gt;&lt;span&gt;__rvm_rm_rf already gone: &#x2F;home&#x2F;steve&#x2F;.rvm&#x2F;tmp&#x2F;55505*
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The lack of &lt;code&gt;sudo&lt;&#x2F;code&gt; is mentioned so it may be related to the failure.&lt;&#x2F;p&gt;
&lt;p&gt;Consulting the log message mentioned in the error:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;Downloading bundled gem files...
&lt;&#x2F;span&gt;&lt;span&gt;executable host ruby is required. use --with-baseruby option.
&lt;&#x2F;span&gt;&lt;span&gt;*** Error code 1
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;So we need a native ruby in order to install ruby through RVM.&lt;&#x2F;p&gt;
&lt;p&gt;As root, I ran:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;# pkg install ruby
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Then to toll RVM to stop trying to install dependencies, run&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;$ rvm autolibs disable
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Now run this command again:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;$ rvm install ruby-3 --disable-binary
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;…and ruby should now be installed!&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Unsafely removing disks in a ZFS pool</title>
          <pubDate>Fri, 16 Oct 2020 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/daily/unsafely-removing-disks-in-a-zfs-pool/</link>
          <guid>https://www.gridbugs.org/daily/unsafely-removing-disks-in-a-zfs-pool/</guid>
          <description xml:base="https://www.gridbugs.org/daily/unsafely-removing-disks-in-a-zfs-pool/">&lt;p&gt;Here’s a little experiment I did to familiarise myself with ZFS on FreeBSD 12.1.
Start with a mirror (“foo”) made up of 2 disks (some spare external hard drives I had lying around):&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;+root@fontaine ~ # zpool status foo
&lt;&#x2F;span&gt;&lt;span&gt;  pool: foo
&lt;&#x2F;span&gt;&lt;span&gt; state: ONLINE
&lt;&#x2F;span&gt;&lt;span&gt;  scan: resilvered 80K in 0 days 00:00:01 with 0 errors on Sat Oct 17 00:34:15 2020
&lt;&#x2F;span&gt;&lt;span&gt;config:
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;        NAME        STATE     READ WRITE CKSUM
&lt;&#x2F;span&gt;&lt;span&gt;        foo         ONLINE       0     0     0
&lt;&#x2F;span&gt;&lt;span&gt;          mirror-0  ONLINE       0     0     0
&lt;&#x2F;span&gt;&lt;span&gt;            da1     ONLINE       0     0     0
&lt;&#x2F;span&gt;&lt;span&gt;            da2     ONLINE       0     0     0
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;errors: No known data errors
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Now yank out the cable attaching &lt;code&gt;da1&lt;&#x2F;code&gt;!&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;+root@fontaine ~ # zpool status foo
&lt;&#x2F;span&gt;&lt;span&gt;  pool: foo
&lt;&#x2F;span&gt;&lt;span&gt; state: DEGRADED
&lt;&#x2F;span&gt;&lt;span&gt;status: One or more devices has been removed by the administrator.
&lt;&#x2F;span&gt;&lt;span&gt;        Sufficient replicas exist for the pool to continue functioning in a
&lt;&#x2F;span&gt;&lt;span&gt;        degraded state.
&lt;&#x2F;span&gt;&lt;span&gt;action: Online the device using &amp;#39;zpool online&amp;#39; or replace the device with
&lt;&#x2F;span&gt;&lt;span&gt;        &amp;#39;zpool replace&amp;#39;.
&lt;&#x2F;span&gt;&lt;span&gt;  scan: resilvered 80K in 0 days 00:00:01 with 0 errors on Sat Oct 17 00:34:15 2020
&lt;&#x2F;span&gt;&lt;span&gt;config:
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;        NAME                      STATE     READ WRITE CKSUM
&lt;&#x2F;span&gt;&lt;span&gt;        foo                       DEGRADED     0     0     0
&lt;&#x2F;span&gt;&lt;span&gt;          mirror-0                DEGRADED     0     0     0
&lt;&#x2F;span&gt;&lt;span&gt;            10400537847491037026  REMOVED      0     0     0  was &#x2F;dev&#x2F;da1
&lt;&#x2F;span&gt;&lt;span&gt;            da2                   ONLINE       0     0     0
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;errors: No known data errors
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The data on all datasets of the zpool “foo” is still accessible. You can continue using
the pool in this state, and any changes made to its contents will be applied to the
unplugged disk when we re-attach it.&lt;&#x2F;p&gt;
&lt;p&gt;The point of unplugging the disk was to simulate a disk failure, but when we plug the
disk back in, ZFS is smart enough to notice that it belongs in the “foo” pool. Try as
I might, I couldn’t convince ZFS to &lt;code&gt;replace&lt;&#x2F;code&gt; the disk with itself. Nor could I wipe
the disk (with &lt;code&gt;gpart&lt;&#x2F;code&gt;, say), because something (I presume ZFS) starts using the disk
the second I plug it back in. That kind of messed up my experiment, but I suppose it’s
a convenient feature.&lt;&#x2F;p&gt;
&lt;p&gt;So to complete this scenario, before plugging the disk back in, run:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;+root@fontaine ~ # zpool offline foo da1
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This step isn’t necessary for this scenario. When we eventually plug the disk back in,
running &lt;code&gt;zpool online foo da1&lt;&#x2F;code&gt; will take the disk straight from &lt;code&gt;REMOVED&lt;&#x2F;code&gt; to &lt;code&gt;ONLINE&lt;&#x2F;code&gt;.
Leaving this step here as in a more realistic disk failure it might be necessary.&lt;&#x2F;p&gt;
&lt;p&gt;Status will now be:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;+root@fontaine ~ # zpool status foo
&lt;&#x2F;span&gt;&lt;span&gt;  pool: foo
&lt;&#x2F;span&gt;&lt;span&gt; state: DEGRADED
&lt;&#x2F;span&gt;&lt;span&gt;status: One or more devices has been taken offline by the administrator.
&lt;&#x2F;span&gt;&lt;span&gt;        Sufficient replicas exist for the pool to continue functioning in a
&lt;&#x2F;span&gt;&lt;span&gt;        degraded state.
&lt;&#x2F;span&gt;&lt;span&gt;action: Online the device using &amp;#39;zpool online&amp;#39; or replace the device with
&lt;&#x2F;span&gt;&lt;span&gt;        &amp;#39;zpool replace&amp;#39;.
&lt;&#x2F;span&gt;&lt;span&gt;  scan: resilvered 80K in 0 days 00:00:01 with 0 errors on Sat Oct 17 00:34:15 2020
&lt;&#x2F;span&gt;&lt;span&gt;config:
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;        NAME                      STATE     READ WRITE CKSUM
&lt;&#x2F;span&gt;&lt;span&gt;        foo                       DEGRADED     0     0     0
&lt;&#x2F;span&gt;&lt;span&gt;          mirror-0                DEGRADED     0     0     0
&lt;&#x2F;span&gt;&lt;span&gt;            10400537847491037026  OFFLINE      0     0     0  was &#x2F;dev&#x2F;da1
&lt;&#x2F;span&gt;&lt;span&gt;            da2                   ONLINE       0     0     0
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;errors: No known data errors
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Note the &lt;code&gt;da1&lt;&#x2F;code&gt; line has changed &lt;code&gt;STATE&lt;&#x2F;code&gt; from &lt;code&gt;REMOVED&lt;&#x2F;code&gt; to &lt;code&gt;OFFLINE&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Plug the disk back in, and run:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;+root@fontaine ~ # zpool online foo da1
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;And all is well:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;+root@fontaine ~ # zpool status foo
&lt;&#x2F;span&gt;&lt;span&gt;  pool: foo
&lt;&#x2F;span&gt;&lt;span&gt; state: ONLINE
&lt;&#x2F;span&gt;&lt;span&gt;  scan: resilvered 48K in 0 days 00:00:01 with 0 errors on Sat Oct 17 00:49:16 2020
&lt;&#x2F;span&gt;&lt;span&gt;config:
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;        NAME        STATE     READ WRITE CKSUM
&lt;&#x2F;span&gt;&lt;span&gt;        foo         ONLINE       0     0     0
&lt;&#x2F;span&gt;&lt;span&gt;          mirror-0  ONLINE       0     0     0
&lt;&#x2F;span&gt;&lt;span&gt;            da1     ONLINE       0     0     0
&lt;&#x2F;span&gt;&lt;span&gt;            da2     ONLINE       0     0     0
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;errors: No known data errors
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
</description>
      </item>
      <item>
          <title>Yes WWW</title>
          <pubDate>Thu, 15 Oct 2020 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/daily/yes-www/</link>
          <guid>https://www.gridbugs.org/daily/yes-www/</guid>
          <description xml:base="https://www.gridbugs.org/daily/yes-www/">&lt;p&gt;Recently I changed the server config for this site such that
upon navigating to “gridbugs.org”, you are forwarded to
“www.gridbugs.org”. This was in response to reading
&lt;a href=&quot;https:&#x2F;&#x2F;www.yes-www.org&#x2F;why-use-www&#x2F;&quot;&gt;www.yes-www.org&lt;&#x2F;a&gt;
which explains the technical (and societal!) benefits to serving web content
from a subdomain (such as “www” but it can of course, be anything),
rather than a naked domain (such as “gridbugs.org”).&lt;&#x2F;p&gt;
&lt;p&gt;In the past I’ve used naked domains for my main sites, and
subdomains for spin-offs (such as my browser games at
&lt;a href=&quot;https:&#x2F;&#x2F;games.gridbugs.org&quot;&gt;games.gridbugs.org&lt;&#x2F;a&gt;.
I considered my reason for this and concluded that “it’s because short URLs are sexy”
and upon realising this, promptly halted the practice.&lt;&#x2F;p&gt;
&lt;p&gt;Well sort of. It &lt;em&gt;is&lt;&#x2F;em&gt; true that short URLs are sexy, but as long as web traffic
reaching the naked domain gets forwarded to the “www” (or whatever) subdomain,
you can still impress your friends with short URLs. They likely won’t even notice
the sneaky “www.” prepended to the domain name when they reach your site.&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>What if all your boot disks suddenly got wiped?</title>
          <pubDate>Wed, 14 Oct 2020 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/daily/what-if-all-your-boot-disks-suddenly-got-wiped/</link>
          <guid>https://www.gridbugs.org/daily/what-if-all-your-boot-disks-suddenly-got-wiped/</guid>
          <description xml:base="https://www.gridbugs.org/daily/what-if-all-your-boot-disks-suddenly-got-wiped/">&lt;p&gt;The other night, by a string of coincidences, I had an experience where
every hard drive I tried to use would fail. I’ve since recovered most of
them, but it got me thinking about how I would recover if all the unix
boot disks in my house were mysteriously wiped.&lt;&#x2F;p&gt;
&lt;p&gt;In at least the last 5 years, I’ve barely spared a thought for CDs, let alone use
one to install an operating system. Thus all my recent unix installs have involved &lt;code&gt;dd&lt;&#x2F;code&gt;-ing
a disk image file onto a USB stick, booting off it, and installing from there.
But &lt;code&gt;dd&lt;&#x2F;code&gt; is a unix program, so unix needs to be bootstrapped from something else.&lt;&#x2F;p&gt;
&lt;p&gt;My first ever unix install was in 2009 and it
involved using PowerISO running on Windows 7 to burn a Ubuntu (Karmic Koala I think)
install CD. At some point shortly after this, I started using Linux as a daily driver,
and a few years later optical disks faded into irrelevance. From that point onwards,
there’s an unbroken chain of using an existing unix installation to &lt;code&gt;dd&lt;&#x2F;code&gt; an install
environment for my next unix installation, though I suppose it’s
possible this chain was interrupted
by a mac with pre-installed MacOS at some point.&lt;&#x2F;p&gt;
&lt;p&gt;In practice, at some point, probably around 2013, I stopped distro-hopping and
settled on archlinux. I &lt;code&gt;dd&lt;&#x2F;code&gt;-ed myself an arch install thumb drive which I’ve
carried on my keyring ever since, and I used it for all subsequent installs.
If everything were wiped, and a situation were contrived that prevented me from
using a computer at work, or a friend’s computer to download and &lt;code&gt;dd&lt;&#x2F;code&gt; an install image?
Well I think I have an old DVD drive lying around, and a case of old CDs which I
think includes a Ubuntu Karmic Koala boot disk.&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>I broke some things</title>
          <pubDate>Tue, 13 Oct 2020 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/daily/i-broke-some-things/</link>
          <guid>https://www.gridbugs.org/daily/i-broke-some-things/</guid>
          <description xml:base="https://www.gridbugs.org/daily/i-broke-some-things/">&lt;p&gt;Last night I tried to set up a media server and managed to break just about
everything I touched. To start with the obvious, I got some hard drives mixed
up and accidentally wiped out the start of the disk where I store all my media.
That’s recoverable - I have some stuff backed up and it can all be downloaded
again with little time and effort.&lt;&#x2F;p&gt;
&lt;p&gt;Next up, I tried installing an old hard drive I had lying around, in the old
PC that I was attempting to set up as a media server. After plugging in the drive,
and getting half-way through the FreeBSD installation, the drive appeared to fail.
I went to restart, and the PC wouldn’t power on at all.
I tried all the obvious things and nothing worked. About an hour later it appeared
to recover on its own.&lt;&#x2F;p&gt;
&lt;p&gt;Finally I managed to get FreeBSD installed on an external hard drive. I booted it
up and started installing the packages I need for my media server. I attached a
brand new disk that I intended to fill with media, but my attempt to create and
format a partition failed, and then the disk stopped showing up at all in subsequent
reboots or in the Windows dual-boot that I maintain (on a separate disk).&lt;&#x2F;p&gt;
&lt;p&gt;Shortly afterwards the external hard drive I was running FreeBSD off stopped showing
up as a boot option. I haven’t dug too deeply into this yet.&lt;&#x2F;p&gt;
&lt;p&gt;The most worrying thing about this whole experience is the hour when the machine
didn’t turn on at all, and the fact that it recovered on its own. I suspect its
power supply is on the way out - it is about 8 years old, and the advise is to
replace power supplies after about 5 years.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;a href=&quot;https:&#x2F;&#x2F;xkcd.com&#x2F;349&#x2F;&quot;&gt;Gratuitous xkcd:&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;daily&#x2F;i-broke-some-things&#x2F;success.png&quot; alt=&quot;success.png&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Formatting GUIDs in GPT header fields</title>
          <pubDate>Mon, 12 Oct 2020 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/daily/formatting-guids-in-gpt-header-fields/</link>
          <guid>https://www.gridbugs.org/daily/formatting-guids-in-gpt-header-fields/</guid>
          <description xml:base="https://www.gridbugs.org/daily/formatting-guids-in-gpt-header-fields/">&lt;p&gt;“GPT” stands for “GUID Partition Table”, and “GUID” stands for “Globally-Unique IDentifier”.
A GUID is a number generated according to the GUID specification, which is a method for
generating numbers such that it is very unlikely that any two thus generated numbers will
be the same.&lt;&#x2F;p&gt;
&lt;p&gt;GPT uses GUIDs to identify disks, partitions, and partition &lt;em&gt;types&lt;&#x2F;em&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;One normally writes a GUID as several dash-separated hexidecimal numbers.
An example GUID is &lt;code&gt;C12A7328-F81F-11D2-BA4B-00A0C93EC93B&lt;&#x2F;code&gt;. This particular
GUID is special, in that it indicates an “EFI System Partition” partition type.
This is the partition type that my tool will create, as it’s aimed at generating
UEFI-bootable disk images.&lt;&#x2F;p&gt;
&lt;p&gt;Looking at an example UEFI-bootable disk, the &lt;em&gt;numeric&lt;&#x2F;em&gt; value for its partition type
is &lt;code&gt;0x3bc93ec9a0004bba11d2f81fc12a7328&lt;&#x2F;code&gt;. Looking closely at this number and the EFI GUID
&lt;code&gt;C12A7328-F81F-11D2-BA4B-00A0C93EC93B&lt;&#x2F;code&gt;, every byte (pair of hex digits) is present. The
order of fields is reversed, and the order of bytes within &lt;em&gt;some&lt;&#x2F;em&gt; fields is reversed.
I need to do some more digging to find out the general rule for encoding GUIDs in GPT
headers, though I suspect the pattern observable from this one example will suffice.&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Encoding disk headers into disk images</title>
          <pubDate>Sun, 11 Oct 2020 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/daily/encoding-disk-headers-into-disk-images/</link>
          <guid>https://www.gridbugs.org/daily/encoding-disk-headers-into-disk-images/</guid>
          <description xml:base="https://www.gridbugs.org/daily/encoding-disk-headers-into-disk-images/">&lt;p&gt;My first attempt at implementing disk image creation feels messy.
Each type of header has a corresponding &lt;code&gt;struct&lt;&#x2F;code&gt; in the code base.
When reading headers from a disk, populating a &lt;code&gt;struct&lt;&#x2F;code&gt; with their
fields is an easy way to debug (in rust one can just &lt;code&gt;[derive(Debug)]&lt;&#x2F;code&gt;
on the &lt;code&gt;struct&lt;&#x2F;code&gt; and print each field name&#x2F;value pair).
For generating headers for new disk images however, populating the
header &lt;code&gt;struct&lt;&#x2F;code&gt; first then encoding it into a disk image file feels
like redundant work. In particular, when some of the header fields
are meant to contain a checksum of th remaining fields (as is the case
in GPT headers), these fields can only be populated once the header
is encoded as a byte array anyway. In such cases, I suspect I’ll have
an easier time generating the byte array directly rather than going
via a header &lt;code&gt;struct&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Gradually implementing a tool to create GPT FAT disk images</title>
          <pubDate>Sat, 10 Oct 2020 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/daily/gradually-implementing-a-tool-to-create-gpt-fat-disk-images/</link>
          <guid>https://www.gridbugs.org/daily/gradually-implementing-a-tool-to-create-gpt-fat-disk-images/</guid>
          <description xml:base="https://www.gridbugs.org/daily/gradually-implementing-a-tool-to-create-gpt-fat-disk-images/">&lt;p&gt;I’m slowly getting there. Today I added the ability to write the “Protective MBR”
to a file - the first 512 bytes of any GPT disk image. The only information it
contains is the size of the disk. Next up will be writing the GPT header, and
partition entry array.&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>All game links on this site now point to itch.io</title>
          <pubDate>Fri, 09 Oct 2020 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/daily/all-game-links-on-this-site-now-point-to-itch-io/</link>
          <guid>https://www.gridbugs.org/daily/all-game-links-on-this-site-now-point-to-itch-io/</guid>
          <description xml:base="https://www.gridbugs.org/daily/all-game-links-on-this-site-now-point-to-itch-io/">&lt;p&gt;I updated all the posts on this site that previously linked to files.gridbugs.org or games.gridbugs.org
to point to &lt;a href=&quot;https:&#x2F;&#x2F;gridbugs.itch.io&#x2F;&quot;&gt;my itch.io page&lt;&#x2F;a&gt; instead. Some exceptions are works in progress,
and abandoned projects that I never plan to release, such as my &lt;a href=&quot;https:&#x2F;&#x2F;games.gridbugs.org&#x2F;abandoned-game-big&#x2F;&quot;&gt;top down side-scrolling engine&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>More of my 7DRLs are playable on itch.io</title>
          <pubDate>Thu, 08 Oct 2020 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/daily/more-of-my-7drls-are-playable-on-itch-io/</link>
          <guid>https://www.gridbugs.org/daily/more-of-my-7drls-are-playable-on-itch-io/</guid>
          <description xml:base="https://www.gridbugs.org/daily/more-of-my-7drls-are-playable-on-itch-io/">&lt;p&gt;Four of the five games I’ve made in the 7DRL game jam can be played in a browser,
and all are available for download on &lt;a href=&quot;https:&#x2F;&#x2F;gridbugs.itch.io&#x2F;&quot;&gt;my itch.io page&lt;&#x2F;a&gt;,
but in most cases I’d neglected to take advantage of the feature of itch that lets
browser version of the game be hosted in itch itself. When releasing games on itch
in the past, I’d uploaded downloadable versions of games, but elected to host the
browser versions on my own website and just include a link on the itch page for the game.&lt;&#x2F;p&gt;
&lt;p&gt;Tonight I uploaded in-browser-playable versions of all my 7DRLs (except for “Apocalypse Post” which can’t
be played in a browser), and set up itch so that the main page for each game has a button that starts an
itch-hosted in-browser version of the game.
I continue to host my games on &lt;a href=&quot;https:&#x2F;&#x2F;games.gridbugs.org&quot;&gt;games.gridbugs.org&lt;&#x2F;a&gt;
(which now points to my new nearlyfreespeech site instead of AWS!), but I’m planning
to update all the links on this site an in the games’ git repos to refer itch instead.&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Migrated browser games to nearlyfreespeech</title>
          <pubDate>Wed, 07 Oct 2020 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/daily/migrated-browser-games-to-nearlyfreespeech/</link>
          <guid>https://www.gridbugs.org/daily/migrated-browser-games-to-nearlyfreespeech/</guid>
          <description xml:base="https://www.gridbugs.org/daily/migrated-browser-games-to-nearlyfreespeech/">&lt;p&gt;After &lt;a href=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;daily&#x2F;i-accidentally-let-some-of-my-tls-certs-expire&#x2F;&quot;&gt;I accidentally let all my AWS TLS certs expire&lt;&#x2F;a&gt;, attempting to
play any games on games.gridbugs.org (such as &lt;a href=&quot;https:&#x2F;&#x2F;games.gridbugs.org&#x2F;skeleton-crew&quot;&gt;Skeleton Crew&lt;&#x2F;a&gt;)
results in a scary warning about security risks ahead. I took this opportunity to move more of my
stuff off amazon and onto my new favourite web hosting provider: &lt;a href=&quot;https:&#x2F;&#x2F;www.nearlyfreespeech.net&#x2F;&quot;&gt;nearlyfreespeech&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Currently all my browser games are playable on &lt;a href=&quot;https:&#x2F;&#x2F;gridbugs-games.nfshost.com&#x2F;&quot;&gt;gridbugs-games.nfshost.com&lt;&#x2F;a&gt;.
I’ll update the DNS records for games.gridbugs.org in the coming days.
As an added bonus, there is now an index page (just the default apache index) which lists all the games!
This wasn’t possible when I was hosting with AWS because S3 has no concept of listing the files in a directory
when a directory lacks an index file.&lt;&#x2F;p&gt;
&lt;p&gt;I removed a work-in-progress game (“RIP”), renamed “meters” to “meters-below-the-ground”, and compressed the bump
and light map in the “top-down-side-scrolling-engine”, which I’ve also renamed to “abandoned-game-big”.
There are some references to these projects in various git repos and pages on this site, which I still need to
update.&lt;&#x2F;p&gt;
&lt;p&gt;I’m not going to migrate downloadable files for the games that are distributed as standalone binaries.
Instead, I’ll update all their download links to point to &lt;a href=&quot;https:&#x2F;&#x2F;gridbugs.itch.io&#x2F;&quot;&gt;my itch.io page&lt;&#x2F;a&gt; where
they can be downloaded (for an optional fee!), and where I don’t have to pay for the bandwidth!&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>FAT32 FSInfo and its backup don&#x27;t always match</title>
          <pubDate>Tue, 06 Oct 2020 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/daily/fat32-fsinfo-and-its-backup-don-t-always-match/</link>
          <guid>https://www.gridbugs.org/daily/fat32-fsinfo-and-its-backup-don-t-always-match/</guid>
          <description xml:base="https://www.gridbugs.org/daily/fat32-fsinfo-and-its-backup-don-t-always-match/">&lt;p&gt;FAT32 defines a section named FSInfo at the start of the partition where the number of free
clusters, and the index of the next free clusters, are stored.
The spec stresses that this section is meant as a hint only, and implementations
must not rely on this information being accurate.
A second section contains a backup of FSInfo.&lt;&#x2F;p&gt;
&lt;p&gt;To learn more about FAT32, I formatted a partition on a USB stick on Linux using &lt;code&gt;mkfs.vfat&lt;&#x2F;code&gt;
and then &lt;code&gt;dd&lt;&#x2F;code&gt;’d the disk into a file. I did the same thing with another USB stick on FreeBSD
using &lt;code&gt;newfs_msdos&lt;&#x2F;code&gt;, and the FSInfo and backup FSInfo sections contain different values.
They are otherwise valid FSInfo sections; all the signatures are valid. But the data fields
differ.&lt;&#x2F;p&gt;
&lt;p&gt;I’ve updated my disk image parsing tool to not care if the FSInfo sections don’t match.
I’m hoping this doesn’t mean that I’ve overlooked an important detail in the FAT32 spec.&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>In praise of rEFInd</title>
          <pubDate>Mon, 05 Oct 2020 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/daily/in-praise-of-refind/</link>
          <guid>https://www.gridbugs.org/daily/in-praise-of-refind/</guid>
          <description xml:base="https://www.gridbugs.org/daily/in-praise-of-refind/">&lt;p&gt;In a recent update, Windows saw fit to mess with my EFI (boot) partition in such a way
that my Windows&#x2F;Linux dual-boot setup no longer functioned. When I turned on my PC, it
booted straight to Windows, rather than showing the GRUB menu first.
Poking around in my motherboard’s UEFI BIOS tool, and it appears “Secure Boot” has been
enabled, and some keys installed. I’m 90% sure that I didn’t set it up that way when I
built my PC about 5 years ago. The “default” boot image on my EFI partition also appeared
to have been updated in the last few days. Suspicious.&lt;&#x2F;p&gt;
&lt;p&gt;I tried setting the “default” boot image (“\EFI\bootx64.efi”) back to the grub image,
but this had no effect. Not too surprising - not all implementations follow this convention.
I then disabled secure boot. The only way to do this using my motherboard’s UEFI tool is to
delete the secure boot keys that Windows update seems to have installed. This made me a little
nervous, but if I had to pick between having a broken Windows install and a broken Linux install,
I’d forego Windows - all I use it for is playing games anyway.&lt;&#x2F;p&gt;
&lt;p&gt;I totally planned on breaking out an Archlinux live USB at this point and re-configuring UEFI,
but before going down this potential rabbit hole, I tried setting up the &lt;a href=&quot;http:&#x2F;&#x2F;www.rodsbooks.com&#x2F;refind&#x2F;&quot;&gt;rEFInd&lt;&#x2F;a&gt;
bootloader. It’s a tool that locates all the bootable images on in your EFI partition, and displays
a graphical menu to choose which one to boot. The &lt;a href=&quot;http:&#x2F;&#x2F;www.rodsbooks.com&#x2F;refind&#x2F;installing.html#windows&quot;&gt;Windows Installation Instructions&lt;&#x2F;a&gt;
worked perfectly, though the first time I rebooted after installing rEFInd it took me straight to Windows,
and the second time I forced it to run rEFInd via my motherboard’s UEFI tool. Now it runs rEFInd first.&lt;&#x2F;p&gt;
&lt;p&gt;A small quality of life feature I’m appreciative of is rEFInd remembers your last choice and selects it upon
subsequent boots. GRUB may have this setting but I never thought to look for it. This will mean that when Windows
decides to update and restart next time, it won’t reboot into Linux (the default selection in my GRUB menu), but
instead boot back into Windows to continue the update.&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Getting back into bouldering</title>
          <pubDate>Sun, 04 Oct 2020 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/daily/getting-back-into-bouldering/</link>
          <guid>https://www.gridbugs.org/daily/getting-back-into-bouldering/</guid>
          <description xml:base="https://www.gridbugs.org/daily/getting-back-into-bouldering/">&lt;p&gt;During the peak of covid in NSW I stopped bouldering.
Yesterday I climbed for the first time in about 3 months.
My muscle memory still remembers how to climb, but my muscles themselves,
unsurprisingly, are struggling, especially my forearms.
I also seem to tire much more quickly than before.
This will pass. I’m going to start climbing weekly.&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>I accidentally let some of my TLS certs expire</title>
          <pubDate>Sat, 03 Oct 2020 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/daily/i-accidentally-let-some-of-my-tls-certs-expire/</link>
          <guid>https://www.gridbugs.org/daily/i-accidentally-let-some-of-my-tls-certs-expire/</guid>
          <description xml:base="https://www.gridbugs.org/daily/i-accidentally-let-some-of-my-tls-certs-expire/">&lt;p&gt;A few months ago I switched this site’s web hosting from AWS to nearlyfreespeech.
I set up new TLS certificates for gridbugs.org and www.gridbugs.org, which the site
now uses instead of the AWS certificates it used to use. So when I received warnings
from Amazon informing me that my TLS certificates were about to expire, I promptly
ignored them. I forgot that some additional domains - games.gridbugs.org and
files.gridbugs.org - are still hosted on AWS and still use the (now expired)
AWS certificates. Oops!&lt;&#x2F;p&gt;
&lt;p&gt;I use those domains to host games I’ve made, both downloadable and playable in-browser.
Rather than migrating all the games to nearlyfreespeech, I’ll set up redirects to
my &lt;a href=&quot;https:&#x2F;&#x2F;gridbugs.itch.io&#x2F;&quot;&gt;itch.io&lt;&#x2F;a&gt; page for all the games which I’ve released on
itch. The games which I never released, but which can only be played in a browser (mostly simple demos),
I’ll migrate to nearlyfreespeech. Small game demos that run natively, I’ll migrate as well.
All the new game projects I work on, I’ll make available exclusively through itch.
This is mostly because as I add music to newer games I make, their size can be large, and I don’t
want to use a ton of bandwidth should any of my games become wildly successful.&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>KID___PER? (2,4,3,3,6)</title>
          <pubDate>Fri, 02 Oct 2020 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/daily/kid-per-24336/</link>
          <guid>https://www.gridbugs.org/daily/kid-per-24336/</guid>
          <description xml:base="https://www.gridbugs.org/daily/kid-per-24336/">&lt;p&gt;I look forward to the non-standard clues in &lt;a href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;David_Astle&quot;&gt;DA&lt;&#x2F;a&gt;’s
cryptic crossword in the SMH on Fridays.
I was delighted to see this clue today, simply because it shuns the conventional format for
clues (hence the “?” - there’s no distinction between the “straight part” ahd “cryptic part” of the clue).&lt;&#x2F;p&gt;
&lt;p&gt;The solution appears to be “NO REST FOR THE WICKED”, as the word “NAP” (“REST”) is removed from the
work “KIDNAPPER” in the clue, and witches, who are wicked, steal children (at least in Hansel and Gretel).
This required more “leaps” than most clues which I think is why I got such a rush of endorphins
upon solving it.&lt;&#x2F;p&gt;
&lt;p&gt;Reflecting on this, it feels like an example of &lt;a href=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;daily&#x2F;is-it-a-good-idea-or-are-you-just-proud-to-understand-it&#x2F;&quot;&gt;lending value to something just because it’s hard&lt;&#x2F;a&gt;.
It was satisfying to work out the answer to this clue, but rationally speaking, is it a “good clue”?
Does it even make sense to talk about “good clues” outside of the fact that solving them makes me happy?
Should I add it to &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;gridbugs&#x2F;cryptic-clues&quot;&gt;my list&lt;&#x2F;a&gt;?
“Good” or not, I think I’ll add it as an example of how unusual clues marked with a question mark can be.&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Determining the size of a FAT32 partition</title>
          <pubDate>Thu, 01 Oct 2020 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/daily/determining-the-size-of-a-fat32-partition/</link>
          <guid>https://www.gridbugs.org/daily/determining-the-size-of-a-fat32-partition/</guid>
          <description xml:base="https://www.gridbugs.org/daily/determining-the-size-of-a-fat32-partition/">&lt;p&gt;I’m making a program that takes a list of files as an argument and creates a GPT&#x2F;FAT32 disk
image that contains them. The first step is determining how big the disk image needs to be.
This depends on the size of the files that will occupy it. I take all the files, determine
their size padded to the size of a FAT32 allocation unit (called a “cluster”). For each file,
the user of my program can specify a path within the disk image where the file will be placed.
I determine the number of entries in each directory implied by the path to each file, and how
many clusters each directory will occupy on disk. Then I calculate the size of the FAT table
and partition headers.&lt;&#x2F;p&gt;
&lt;p&gt;Now that I can find out the amount of storage required for the partition, the next steps will be
writing the GPT header and footer on either side of a zeroed-out partition-sized region.
Then I’ll write the FAT headers, FAT table, and copy the data for each file into the disk image.&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>I no longer feel like an imposter</title>
          <pubDate>Wed, 30 Sep 2020 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/daily/i-no-longer-feel-like-an-imposter/</link>
          <guid>https://www.gridbugs.org/daily/i-no-longer-feel-like-an-imposter/</guid>
          <description xml:base="https://www.gridbugs.org/daily/i-no-longer-feel-like-an-imposter/">&lt;p&gt;I realised several months ago that I don’t feel imposter syndrome at my current job.&lt;&#x2F;p&gt;
&lt;p&gt;A couple of years ago I found myself writing ocaml for a certain trading firm which prides
itself on only hiring the smartest people. I often felt like I
somehow fluked their notoriously difficult interviews, and that I was somehow not
“smart enough” to work there.&lt;&#x2F;p&gt;
&lt;p&gt;I’m trying to understand why this feeling has stopped at my current job. It’s not like I’m not still surrounded
by smart people. When talking about recruiting, management still loves to say how we only hire the best.&lt;&#x2F;p&gt;
&lt;p&gt;Being a startup, I feel like I’ve had a much more direct influence on the
success of this company that anywhere I’ve worked before, so there’s more immediate evidence
that I’m doing something right. If I had doubts about whether I was good enough at my job,
they’ve promptly been quelled.&lt;&#x2F;p&gt;
&lt;p&gt;I also find that I’ve started to reject the notion of “smart
enough”. Since we have so many problems to solve, often it’s more important to write dumb,
unsurprising, maintainable code &lt;em&gt;quickly&lt;&#x2F;em&gt; than it is to implement a perfect solution.
Doing things the “smart way” often means writing code that takes longer to implement, and
is harder for others to understand, often for little practical benefit.&lt;&#x2F;p&gt;
&lt;p&gt;A symptom of imposter syndrome is asking fewer questions for fear of appearing to not know something.
This was debilitating! It’s liberating to no longer get self-conscious about whether my co-workers think
I know something. I’m asking far more questions than I used to and learning a ton. Even outside of work,
I find that when friend says a word I don’t know, nowadays I just ask them what it means.&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>FAT32 FSInfo</title>
          <pubDate>Tue, 29 Sep 2020 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/daily/fat32-fsinfo/</link>
          <guid>https://www.gridbugs.org/daily/fat32-fsinfo/</guid>
          <description xml:base="https://www.gridbugs.org/daily/fat32-fsinfo/">&lt;p&gt;The “FSInfo” sector is the second sector of a FAT32-formatted partition.
It contains a count of the number of free clusters in the partition, and
the index of the next free cluster. I added a FSInfo parser to my disk
image tools, and a new tool &lt;code&gt;info&lt;&#x2F;code&gt; for printing metadata about the disk
and partition to help me understand the header contents of the disk images
I’m analysing.&lt;&#x2F;p&gt;
&lt;p&gt;The next step is to start generating disk images with files inside them.&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Cryptic Clues Repository</title>
          <pubDate>Mon, 28 Sep 2020 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/daily/cryptic-clues-repository/</link>
          <guid>https://www.gridbugs.org/daily/cryptic-clues-repository/</guid>
          <description xml:base="https://www.gridbugs.org/daily/cryptic-clues-repository/">&lt;p&gt;I’ve started a git repository for storing my favourite cryptic crossword clues.
I try to solve at least one cryptic crossword per week, and I’d like to gradually
build up a list of the clues I find most entertaining, and instructions of how
to solve them. For now I’m storing clues and their solutions in a markdown file,
but I’ll likely change this to a structured format like yaml or toml so I can
write simple queries later on. The repo is &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;gridbugs&#x2F;cryptic-clues&quot;&gt;here&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>I saw an eel in Moore Park</title>
          <pubDate>Sun, 27 Sep 2020 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/daily/i-saw-an-eel-in-moore-park/</link>
          <guid>https://www.gridbugs.org/daily/i-saw-an-eel-in-moore-park/</guid>
          <description xml:base="https://www.gridbugs.org/daily/i-saw-an-eel-in-moore-park/">&lt;p&gt;A few weeks ago I saw an eel in the Moore Park pond, floating partially
out of the water. I wasn’t sure it was alive so I tapped it with a stick
and it swam away and alarmed a nearby swan. I haven’t seen the eel since.
I hope it’s ok.&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Have I been using vi keys wrong my whole life?</title>
          <pubDate>Sat, 26 Sep 2020 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/daily/have-i-been-using-vi-keys-wrong-my-whole-life/</link>
          <guid>https://www.gridbugs.org/daily/have-i-been-using-vi-keys-wrong-my-whole-life/</guid>
          <description xml:base="https://www.gridbugs.org/daily/have-i-been-using-vi-keys-wrong-my-whole-life/">&lt;p&gt;Or at least the 10 years since I started using Vim?&lt;&#x2F;p&gt;
&lt;p&gt;Back in my dvorak days (read: until about a month ago), I would rebind
“HTNS” (the “JKL;” keys) to the vi navigation keys (typically “HJKL”).
Since switching to qwerty, I adopted the habit of moving my right hand
over the “HJKL” keys when in normal mode, and moving it back to “JKL;”
when switching to insert mode.&lt;&#x2F;p&gt;
&lt;p&gt;Today it was pointed out to me that I could try leaving my hand on the
“JKL;” keys in normal mode, and reach my index finger over to press the
“H” key when I want to navigate to the left. My right index finger now
presses both the “J” and “H” keys when navigating, which is the same as
when typing, and seems obvious in hindsight.&lt;&#x2F;p&gt;
&lt;p&gt;In general I find this change has made it easier to use Vim. The biggest
benefit I’ve noticed is pressing “I” to enter insert mode requires the
same hand movement than pressing “I” to type the letter “I”, as my hand
is in the same place in normal and insert mode.&lt;&#x2F;p&gt;
&lt;p&gt;Hooray for more muscle memory to unlearn and relearn. The fact that I
must now stop and think for a moment before using vi keys will hopefully
mean I use them in fewer circumstances where other forms of navigation would
be more appropriate.&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Crunch</title>
          <pubDate>Fri, 25 Sep 2020 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/daily/crunch/</link>
          <guid>https://www.gridbugs.org/daily/crunch/</guid>
          <description xml:base="https://www.gridbugs.org/daily/crunch/">&lt;p&gt;My last few posts have been general because I haven’t had time to make any
meaningful progress on personal projects due to spending almost
every waking hour at work. I work at a smallish autonomous vehicle startup
with a biggish board meeting next week and everyone is working frantically
to put together some cool demos.&lt;&#x2F;p&gt;
&lt;p&gt;Six months in at my first real startup, the most important lesson I’ve learnt
and put into practice is to not over engineer. I remember a time not so long
ago when I would take pride in building complex things. This job has taught
me the value of simplicity - of building the most basic, stupid version of
the thing you need, and only when it’s absolutely necessary (spoiler: it won’t be)
do you do the extra 80% of the work to squeeze out that last 20% of functionality.&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>What exactly is a &quot;Functional Programming Language&quot;</title>
          <pubDate>Thu, 24 Sep 2020 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/daily/what-exactly-is-a-functional-programming-language/</link>
          <guid>https://www.gridbugs.org/daily/what-exactly-is-a-functional-programming-language/</guid>
          <description xml:base="https://www.gridbugs.org/daily/what-exactly-is-a-functional-programming-language/">&lt;p&gt;I don’t believe the term “functional” is a meaningful description of a programming language.
It’s hard to come up with a definition of the term that includes all the languages that
brand themselves as “functional” that wouldn’t include &lt;em&gt;all&lt;&#x2F;em&gt; modern programming languages.&lt;&#x2F;p&gt;
&lt;p&gt;In a so called functional language, you’ll certainly find closures and first class functions,
but name a general purpose language from the last 30 years that doesn’t have these features.
It’s likely you be &lt;em&gt;encouraged&lt;&#x2F;em&gt; to avoid mutable state, but rare are the languages that
&lt;em&gt;prevent&lt;&#x2F;em&gt; mutation (and these are the &lt;em&gt;purely&lt;&#x2F;em&gt; functional languages - a term which is well-defined)
The standard library will probably come with a collection of persistent, immutable
data structures, but any language can have such a library.&lt;&#x2F;p&gt;
&lt;p&gt;Recursion will likely be the
&lt;em&gt;preferred&lt;&#x2F;em&gt; (or perhaps only) form of iteration, and if you’re lucky the language implementation
(or even the language &lt;em&gt;spec&lt;&#x2F;em&gt; if you’re extra lucky) will employ tail-call optimisation so
you don’t (necessarily!) blow up your stack when calling functions recursively.&lt;&#x2F;p&gt;
&lt;p&gt;Algebraic data types, pattern matching, and expressive type systems all frequently make appearances
in functional languages. Lisp is a notable exception which has none of these.
It doesn’t seem prudent to conflate these terms with functional programming though.&lt;&#x2F;p&gt;
&lt;p&gt;Perhaps “functional” is a useful word for describing a design space attractor. In practice, it
may turn out that all the features listed here go well together. Maybe the term was more meaningful
back in a time when the programming language ecosystem was less diverse, and the functional&#x2F;imperative dichotomy
was stronger. Has the success of functional languages of the past, and their subsequent influence
on the design of new languages, ultimately led to the term losing all meaning?&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Is it a good idea, or are you just proud to understand it?</title>
          <pubDate>Wed, 23 Sep 2020 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/daily/is-it-a-good-idea-or-are-you-just-proud-to-understand-it/</link>
          <guid>https://www.gridbugs.org/daily/is-it-a-good-idea-or-are-you-just-proud-to-understand-it/</guid>
          <description xml:base="https://www.gridbugs.org/daily/is-it-a-good-idea-or-are-you-just-proud-to-understand-it/">&lt;p&gt;I think among engineers there is a tendency to overvalue those ideas
whose complexity can serve as a kind of proof of intellect.
It’s tempting to forgo a simple, easy-to-understand solution,
in place of a terse solution requiring more understanding, or even a more
flowery, perhaps more general solution. It may feel good at first to
show off your knowledge of programming to your fellow engineers who
review your code. Maybe teach them a thing or two. But I claim it’s
rarely merited. Just give a tech talk instead!&lt;&#x2F;p&gt;
&lt;p&gt;I first noticed myself doing this when I came to realize that in scala,
“for loops” behave as syntactic sugar for &lt;code&gt;.map&lt;&#x2F;code&gt;, &lt;code&gt;.flatMap&lt;&#x2F;code&gt;, and &lt;code&gt;.foreach&lt;&#x2F;code&gt;.
For a brief period after this, I over-used them.
It felt “cute” to write a loop over an iterator which just built up a
new iterator (effectively mapping a function over the iterator).&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;scala&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-scala &quot;&gt;&lt;code class=&quot;language-scala&quot; data-lang=&quot;scala&quot;&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;val &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;newIter &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= for &lt;&#x2F;span&gt;&lt;span&gt;{ i &amp;lt;- someIterator } &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;yield &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; do something with i
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This made me feel clever because understanding that code depended on
understanding that this loop is effectively:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;scala&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-scala &quot;&gt;&lt;code class=&quot;language-scala&quot; data-lang=&quot;scala&quot;&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;val &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;newIter &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; someIterator.map(i &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;* do something with i *&#x2F;&lt;&#x2F;span&gt;&lt;span&gt;)
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;…and that the operation on &lt;code&gt;i&lt;&#x2F;code&gt; will be deferred until &lt;code&gt;newIter&lt;&#x2F;code&gt; is actually
iterated (if ever). This was especially confusing because in pretty much
every other language, for loops run immediately in all cases rather than
having behaviour that varies based on the type of the value on the right-hand-side
of the &lt;code&gt;&amp;lt;-&lt;&#x2F;code&gt; symbol (the more I use scala, the more I detest it).&lt;&#x2F;p&gt;
&lt;p&gt;It felt good to write the &lt;code&gt;for&lt;&#x2F;code&gt; version of the code above because I got
to exercise some newfound knowledge. But the requirement of knowledge that
made this code so satisfying to write makes it difficult to read.&lt;&#x2F;p&gt;
&lt;p&gt;So now I’m conscious of this pathology and aspire to write code that’s only
as complicated as it needs to be to solve the problem on hand, rather than
demanding needless cognitive load for cognitive load’s own sake.&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Checking backup regions in gpt fat disk image tools</title>
          <pubDate>Tue, 22 Sep 2020 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/daily/checking-backup-regions-in-gpt-fat-disk-image-tools/</link>
          <guid>https://www.gridbugs.org/daily/checking-backup-regions-in-gpt-fat-disk-image-tools/</guid>
          <description xml:base="https://www.gridbugs.org/daily/checking-backup-regions-in-gpt-fat-disk-image-tools/">&lt;p&gt;The FAT filesystem and GPT partition table both set aside part of the partition and disk for storing a backup
of header information. At the start of the disk, and the start of FAT partitions, there are header data structures
that contain metadata about the data stored in the remainder of the disk&#x2F;partition. The start of a partition&#x2F;disk
is more likely to be accidentally overwritten than the end, so to help recover from such accidents, FAT and GPT
both store copies of their headers at the end of the partition&#x2F;disk as well.&lt;&#x2F;p&gt;
&lt;p&gt;When I first started working on &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;gridbugs&#x2F;gpt-fat-disk-image&quot;&gt;tools for reading FAT partitions from GPT disk images&lt;&#x2F;a&gt;
I added support for reading backup structures, as I was focusing
on completeness. At some point, in the interest of building the simplest thing that would do the job, I removed all
the code for dealing with these regions. Now I’m working on writing disk images rather than just reading them, and I need
to generate backup regions of my own.&lt;&#x2F;p&gt;
&lt;p&gt;To test my understanding of backup regions, I’ll start by adding support for checking the validity of backup regions back to my
tools that read files from FAT&#x2F;GPT disk images.&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Separable Convolution Kernels</title>
          <pubDate>Mon, 21 Sep 2020 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/daily/separable-convolution-kernels/</link>
          <guid>https://www.gridbugs.org/daily/separable-convolution-kernels/</guid>
          <description xml:base="https://www.gridbugs.org/daily/separable-convolution-kernels/">&lt;p&gt;In the field of image processing, &lt;em&gt;convolution&lt;&#x2F;em&gt; refers to an operation which takes an image (a 2d array of pixels)
and computes a new image of the same dimensions by considering each pixel of the input image, and a relatively
small number of nearby pixels, and computing a weighted sum of their values, independently for each channel.
The pixel channel values in the resulting image are these weight sums.
Some typical applications of convolution are blurring and edge sharpening.&lt;&#x2F;p&gt;
&lt;p&gt;The choices of which nearby pixels to consider, and what weights to use in the weighted sum, are dictated by
a &lt;em&gt;convolution kernel&lt;&#x2F;em&gt;, which takes the form of a 2d array of real numbers.
To compute the result of a convolution of a single pixel channel value, centre the kernel on that pixel,
with the “cells” of the kernel aligned to the pixels of the image,
and for each cell of the kernel, multiply the value in the cell with the value in the pixel underneath.
Add up the results.
Convolving an entire image now looks like sliding the kernel over every pixel in the image and computing
all weighted sums. If the kernel is partially off the edge, it’s common to imagine the final row&#x2F;column
of the image continuing forever in all directions (there are alternatives, but they’re out of scope here).&lt;&#x2F;p&gt;
&lt;p&gt;A &lt;em&gt;separable convolution kernel&lt;&#x2F;em&gt; can be expressed as a 1d array. The convolution process now looks a little
different. Rather than sliding a rectangular kernel (a 2d array) around an image, instead, slide a
horizontal strip (a 1d array), and calculate all weighted sums as before. This gives you an intermediate
image of the same size as the input image. Now repeat the process taking the intermediate image as input,
only this time treat the 1d kernel as a &lt;em&gt;vertical&lt;&#x2F;em&gt; strip.&lt;&#x2F;p&gt;
&lt;p&gt;Gaussian blurring is an example of a separable kernel. There is a NxN 2d array that can be convolved with
an image to apply a Gaussian blur effect, however there is also a N-length 1d array that can be convolved
twice as above. The benefit of using the 1d array is that the convolution can be done in &lt;code&gt;2*N*K*C&lt;&#x2F;code&gt; multiplications
for an image with &lt;code&gt;K&lt;&#x2F;code&gt; pixels and &lt;code&gt;C&lt;&#x2F;code&gt; channels, instead of &lt;code&gt;N*N*K*C&lt;&#x2F;code&gt; multiplications as would be required for
the 2d convolution.&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Move semantics and argument parsing</title>
          <pubDate>Sun, 20 Sep 2020 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/daily/move-semantics-and-argument-parsing/</link>
          <guid>https://www.gridbugs.org/daily/move-semantics-and-argument-parsing/</guid>
          <description xml:base="https://www.gridbugs.org/daily/move-semantics-and-argument-parsing/">&lt;p&gt;In my initial design for &lt;a href=&quot;https:&#x2F;&#x2F;crates.io&#x2F;crates&#x2F;meap&quot;&gt;meap&lt;&#x2F;a&gt;, the &lt;code&gt;Parser&lt;&#x2F;code&gt; trait contained this
method which extracts typed values from an untyped low-level parser output:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;parse_low_level&lt;&#x2F;span&gt;&lt;span&gt;(
&lt;&#x2F;span&gt;&lt;span&gt;    self,
&lt;&#x2F;span&gt;&lt;span&gt;    ll: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut &lt;&#x2F;span&gt;&lt;span&gt;low_level::LowLevelParserOutput,
&lt;&#x2F;span&gt;&lt;span&gt;) -&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Result&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;Self::&lt;&#x2F;span&gt;&lt;span&gt;Item, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Box&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;dyn error::Error&amp;gt;&amp;gt;;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Note that the parser is consumed by this method; After calling this method on a parser, the parser
is destroyed. This allows the parser to contain values which move into its parsed output.
Two examples:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;a parser wrapping another parser, giving it a default value. The default value stored in the parser can
be moved into the result in the case where it’s needed, preventing the need to copy the data from the
parser into its output.&lt;&#x2F;li&gt;
&lt;li&gt;the &lt;code&gt;Map&lt;&#x2F;code&gt; combinator, which wraps a parser, and calls a function on its output. I want to make the function
a &lt;code&gt;FnOnce&lt;&#x2F;code&gt; - the most permissive of rust’s function traits - but calling a &lt;code&gt;FnOnce&lt;&#x2F;code&gt; consumes it. If the
mapped function was in a field of the &lt;code&gt;Map&lt;&#x2F;code&gt; combinator, the entire combinator would need to be consumed
in order to call the &lt;code&gt;FnOnce&lt;&#x2F;code&gt;.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;This worked perfectly fine up until I added help messages. If the argument parser finds that its input is
invalid, it stops parsing and prints a help message, including details on arguments that it accepts.
E.g.&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;Usage: sand [OPTIONS] PERIOD
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;Args:
&lt;&#x2F;span&gt;&lt;span&gt;    PERIOD     how long to wait
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;Options:
&lt;&#x2F;span&gt;&lt;span&gt;    [-i, --interval DURATION]     how frequently to update the display (Default: 1s)
&lt;&#x2F;span&gt;&lt;span&gt;    [-h, --help]                  print help message
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The problem is that if we’re part-way through parsing, and encounter an error, part of the
parser has already been consumed by the parsing process, and so can’t be re-traversed to
generate a help message.&lt;&#x2F;p&gt;
&lt;p&gt;This requirement led to me changing the type of the method above:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;parse_low_level&lt;&#x2F;span&gt;&lt;span&gt;(
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut &lt;&#x2F;span&gt;&lt;span&gt;self,
&lt;&#x2F;span&gt;&lt;span&gt;    ll: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut &lt;&#x2F;span&gt;&lt;span&gt;low_level::LowLevelParserOutput,
&lt;&#x2F;span&gt;&lt;span&gt;) -&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Result&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;Self::&lt;&#x2F;span&gt;&lt;span&gt;Item, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Box&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;dyn error::Error&amp;gt;&amp;gt;;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Now the parser is mutably borrowed, so it remains in scope after parsing.
This is slightly unfortunate as now some errors that were previously caught at compile-time
are now caught at runtime. In particular, for parsers that contain some value that is moved
out during parsing, running the parser twice will panic as the necessary values are gone.
Previously it was not possible to run the parser twice, as it was destroyed by running.&lt;&#x2F;p&gt;
&lt;p&gt;Note that running the parser twice is a mistake, but now it’s possible (though still not easy!)
to make this mistake.&lt;&#x2F;p&gt;
&lt;p&gt;The &lt;code&gt;Map&lt;&#x2F;code&gt; combinator is now defined as:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;pub struct &lt;&#x2F;span&gt;&lt;span&gt;Map&amp;lt;T, U, F: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;FnOnce&lt;&#x2F;span&gt;&lt;span&gt;(T) -&amp;gt; U, PT: Parser&amp;lt;Item = T&amp;gt;&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;    f: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Option&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;F&amp;gt;,
&lt;&#x2F;span&gt;&lt;span&gt;    parser_t: PT,
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Its implementation of &lt;code&gt;parse_low_level&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;parse_low_level&lt;&#x2F;span&gt;&lt;span&gt;(
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut &lt;&#x2F;span&gt;&lt;span&gt;self,
&lt;&#x2F;span&gt;&lt;span&gt;    ll: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut &lt;&#x2F;span&gt;&lt;span&gt;low_level::LowLevelParserOutput,
&lt;&#x2F;span&gt;&lt;span&gt;) -&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Result&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;Self::&lt;&#x2F;span&gt;&lt;span&gt;Item, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Box&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;dyn error::Error&amp;gt;&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Ok&lt;&#x2F;span&gt;&lt;span&gt;((self.f.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;take&lt;&#x2F;span&gt;&lt;span&gt;().&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;expect&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;function has already been called&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;))(
&lt;&#x2F;span&gt;&lt;span&gt;        self.&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;parser_t&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;parse_low_level&lt;&#x2F;span&gt;&lt;span&gt;(ll)&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;?&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;    ))
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;A similar technique is employed for default combinators.
Put the “moving out” data inside an &lt;code&gt;Option&lt;&#x2F;code&gt;, and move the data out of the &lt;code&gt;Option&lt;&#x2F;code&gt;
when needed, setting the &lt;code&gt;Option&lt;&#x2F;code&gt; to &lt;code&gt;None&lt;&#x2F;code&gt; in the process, and crashing if it was
already &lt;code&gt;None&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Parsing command-line arguments with meap in real programs</title>
          <pubDate>Sat, 19 Sep 2020 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/daily/parsing-command-line-arguments-with-meap-in-real-programs/</link>
          <guid>https://www.gridbugs.org/daily/parsing-command-line-arguments-with-meap-in-real-programs/</guid>
          <description xml:base="https://www.gridbugs.org/daily/parsing-command-line-arguments-with-meap-in-real-programs/">&lt;p&gt;Now that &lt;a href=&quot;https:&#x2F;&#x2F;crates.io&#x2F;crates&#x2F;meap&quot;&gt;meap&lt;&#x2F;a&gt; is feature complete, I’ve started
adding it to some personal projects as a replacement for my previous command-line
argument parser - &lt;a href=&quot;https:&#x2F;&#x2F;crates.io&#x2F;crates&#x2F;simon&quot;&gt;simon&lt;&#x2F;a&gt;.
Meap is intentionally more conservative in its features than simon, so switching
was not always straightforward.&lt;&#x2F;p&gt;
&lt;p&gt;The first program I adapted was &lt;a href=&quot;https:&#x2F;&#x2F;crates.io&#x2F;crates&#x2F;sand&quot;&gt;sand&lt;&#x2F;a&gt;, which is a
replacement for &lt;code&gt;sleep&lt;&#x2F;code&gt; that waits for a given amount of time, but gives you a running
update of the amount of time left. It doesn’t have very complicated arguments, so
this was not super interesting.&lt;&#x2F;p&gt;
&lt;p&gt;The second program I changed was &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;gridbugs&#x2F;slime99&quot;&gt;slime99&lt;&#x2F;a&gt;, which
is a game I made for the “7 Day Roguelike” gamejam earlier this year.
This was a little more involved. In particular, it revealed a use case I hadn’t planned for:
choosing at most one of a set of possibilities. When run in a terminal, slime99 accepts an
argument telling what colour space to run in (24-bit rgb, 256 colours, greyscale).
This seemed like a common enough use case, so I added some combinators and a macro to
make this simpler. The code in slime99 that parses this argument now looks like this:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;enum &lt;&#x2F;span&gt;&lt;span&gt;ColEncodeChoice {
&lt;&#x2F;span&gt;&lt;span&gt;    TrueColour,
&lt;&#x2F;span&gt;&lt;span&gt;    Rgb,
&lt;&#x2F;span&gt;&lt;span&gt;    Greyscale,
&lt;&#x2F;span&gt;&lt;span&gt;    Ansi,
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;impl &lt;&#x2F;span&gt;&lt;span&gt;ColEncodeChoice {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;parser&lt;&#x2F;span&gt;&lt;span&gt;() -&amp;gt; impl meap::Parser&amp;lt;Item = &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;Self&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;use &lt;&#x2F;span&gt;&lt;span&gt;meap::Parser;
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;use &lt;&#x2F;span&gt;&lt;span&gt;ColEncodeChoice::&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;*&lt;&#x2F;span&gt;&lt;span&gt;;
&lt;&#x2F;span&gt;&lt;span&gt;        meap::choose_at_most_one&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;!&lt;&#x2F;span&gt;&lt;span&gt;(
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;flag&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;true-colour&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;).&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;some_if&lt;&#x2F;span&gt;&lt;span&gt;(TrueColour),
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;flag&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;rgb&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;).&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;some_if&lt;&#x2F;span&gt;&lt;span&gt;(Rgb),
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;flag&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;greyscale&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;).&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;some_if&lt;&#x2F;span&gt;&lt;span&gt;(Greyscale),
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;flag&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;ansi&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;).&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;some_if&lt;&#x2F;span&gt;&lt;span&gt;(Ansi),
&lt;&#x2F;span&gt;&lt;span&gt;        )
&lt;&#x2F;span&gt;&lt;span&gt;        .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;with_default_general&lt;&#x2F;span&gt;&lt;span&gt;(TrueColour)
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
</description>
      </item>
      <item>
          <title>Problems, not Solutions, Part 2</title>
          <pubDate>Fri, 18 Sep 2020 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/daily/problems-not-solutions-part-2/</link>
          <guid>https://www.gridbugs.org/daily/problems-not-solutions-part-2/</guid>
          <description xml:base="https://www.gridbugs.org/daily/problems-not-solutions-part-2/">&lt;p&gt;In a conversation with some coworkers a few days ago I said something that sounded clever. I’m writing it down
so I don’t forget it.&lt;&#x2F;p&gt;
&lt;p&gt;“If you’re a manager and you want your team to solve a &lt;em&gt;problem&lt;&#x2F;em&gt;, but you prescribe a &lt;em&gt;solution&lt;&#x2F;em&gt;, chances are
your team will implement the solution without actually solving the problem.”&lt;&#x2F;p&gt;
&lt;p&gt;I’ve only ever encountered this pathology from the point of view of an engineer, and recently I’ve noticed myself
getting much better at pushing back when a particular solution is forced upon me. Obedience is a curse that we
all need to unlearn. Whenever anyone on my team blindly goes and does something just because their boss (or &lt;em&gt;their&lt;&#x2F;em&gt; boss)
told them so, we all pay the price of needing to maintain more code that usually only half solves the problem.
The project gets slowed down because people are spending time focussing on satisfying specific requests rather than
just solving the problem in the most appropriate way.&lt;&#x2F;p&gt;
&lt;p&gt;I only hope I remember this advice if I ever find myself in a management position.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;a href=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;daily&#x2F;problems-not-solutions&#x2F;&quot;&gt;Link to Part 1&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Meap Help Messages</title>
          <pubDate>Thu, 17 Sep 2020 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/daily/meap-help-messages/</link>
          <guid>https://www.gridbugs.org/daily/meap-help-messages/</guid>
          <description xml:base="https://www.gridbugs.org/daily/meap-help-messages/">&lt;p&gt;My command-line parsing library - &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;gridbugs&#x2F;meap&quot;&gt;meap&lt;&#x2F;a&gt; -
can now generate help messages such as this:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;$ cargo run --example macro -- -h
&lt;&#x2F;span&gt;&lt;span&gt;    Finished dev [unoptimized + debuginfo] target(s) in 0.02s
&lt;&#x2F;span&gt;&lt;span&gt;     Running `target&#x2F;debug&#x2F;examples&#x2F;macro -h`
&lt;&#x2F;span&gt;&lt;span&gt;Usage: target&#x2F;debug&#x2F;examples&#x2F;macro [OPTIONS] STRING [DURATION ...]
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;Args:
&lt;&#x2F;span&gt;&lt;span&gt;    STRING           a string
&lt;&#x2F;span&gt;&lt;span&gt;    [DURATION ...]
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;Options:
&lt;&#x2F;span&gt;&lt;span&gt;    [-i INT]
&lt;&#x2F;span&gt;&lt;span&gt;    [-f, --flag-with-a-really-long-name]
&lt;&#x2F;span&gt;&lt;span&gt;                flag with a really long name
&lt;&#x2F;span&gt;&lt;span&gt;    [-c ...]
&lt;&#x2F;span&gt;&lt;span&gt;    [-h, --help]                     print help message
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This completes the minimal viable set of features for this library, so I did
an &lt;a href=&quot;https:&#x2F;&#x2F;crates.io&#x2F;crates&#x2F;meap&quot;&gt;initial release&lt;&#x2F;a&gt; on crates.io.
Feature-wise, I still need to add default values for optional arguments.
I also want to spend some time writing documentation and tests.&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>roglkpl</title>
          <pubDate>Wed, 16 Sep 2020 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/daily/roglkpl/</link>
          <guid>https://www.gridbugs.org/daily/roglkpl/</guid>
          <description xml:base="https://www.gridbugs.org/daily/roglkpl/">&lt;p&gt;Muscle memory is weird.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;... =&amp;gt; &lt;&#x2F;span&gt;&lt;span&gt;roglkpl!(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;{:?}&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;, args), &lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; rustc: cannot find macro `roglkpl` in this scope
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;code&gt;roglkpl&lt;&#x2F;code&gt; is what you get if you try to type &lt;code&gt;println&lt;&#x2F;code&gt; when someone changes your
keymap from dvorak to qwerty without you realising. Since starting to teach my
hands to type in qwerty about two weeks ago, I occasionally find myself lapsing
back into my old way of typing, and sometimes get a few words in before realising
that what I’m typing makes no sense. It still requires constant conscious effort
to type in qwerty, and a brief lapse in concentration can lead to…&lt;code&gt;roglkpl&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>New phone, and an arduous journey bootstrapping LineageOS</title>
          <pubDate>Tue, 15 Sep 2020 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/daily/new-phone-and-an-arduous-journey-bootstrapping-lineageos/</link>
          <guid>https://www.gridbugs.org/daily/new-phone-and-an-arduous-journey-bootstrapping-lineageos/</guid>
          <description xml:base="https://www.gridbugs.org/daily/new-phone-and-an-arduous-journey-bootstrapping-lineageos/">&lt;p&gt;After 5 years of gradually degrading service (as expected of any phone, sadly), my
LG Nexus 5 succumbed to a stuck power button, as has been the fate of the Nexus 5 of
everyone I know who owned one. I replaced it with a OnePlus 6 (from 2018, refurbished I think),
and naturally I spent the evening installing LineageOS - a custom android ROM.&lt;&#x2F;p&gt;
&lt;p&gt;The install could have gone smoother. Typically, you use &lt;code&gt;fastboot&lt;&#x2F;code&gt; to flash a custom “recovery image”,
boot into recovery mode, and then install the main OS and Google Play (and possibly other Google apps).
Lineage’s recovery image didn’t boot for me - possibly because my firmware was out of date.
I briefly thought I bricked my phone, but then found that powering on with volume-up pressed
takes you directly to fastboot from which you can re-flash the recovery image and try again. I ended up installing
TWRP - an alternative to Lineage’s recovery image - and installing Lineage through that. For whatever reason,
the Google Play installer thought the installed version of android was incorrect (8 instead of 10).
I ended up booting LineageOS without Google Play (which can’t be installed after the OS has booted
the first time for some reason). This incidentally replaced TWRP with Lineage’s recovery image,
and this time I was then able to boot into that, and &lt;em&gt;reinstall&lt;&#x2F;em&gt; LineageOS &lt;em&gt;and&lt;&#x2F;em&gt; Google Play successfully.&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Renamed &quot;args_af&quot; to &quot;meap&quot;</title>
          <pubDate>Mon, 14 Sep 2020 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/daily/renamed-args-af-to-meap/</link>
          <guid>https://www.gridbugs.org/daily/renamed-args-af-to-meap/</guid>
          <description xml:base="https://www.gridbugs.org/daily/renamed-args-af-to-meap/">&lt;p&gt;I renamed my &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;gridbugs&#x2F;meap&quot;&gt;work-in-progress command-line argument parsing library&lt;&#x2F;a&gt;
from “args_af” to “meap”. Meap stands for &lt;strong&gt;M&lt;&#x2F;strong&gt;inimal &lt;strong&gt;E&lt;&#x2F;strong&gt;xtensible &lt;strong&gt;A&lt;&#x2F;strong&gt;rgument &lt;strong&gt;P&lt;&#x2F;strong&gt;arser.
It’s minimal in the sense that it has no external dependencies and CLOCs in at a little over
1000 lines of rust. It’s extensible in the sense that the &lt;code&gt;Parser&lt;&#x2F;code&gt; trait defined in the library
can be implemented by types from 3rd party code to create custom combinators.
A second trait, &lt;code&gt;SingleArgParser&lt;&#x2F;code&gt;, can be used to implement custom basic arguments.&lt;&#x2F;p&gt;
&lt;p&gt;The main reason for the rename is the “AF” in “args_af” stood for “Applicative Functor”, which
is a term often found in “functional programming” literature to describe types which a function
of multiple arguments may be (essentially) mapped. An argument parser is an applicative functor because it’s
possible to take a pair of separate parsers and combine them into a single parser which yields
a pair of values (&lt;code&gt;both&lt;&#x2F;code&gt;), AND it’s possible to take a parser yielding values of some type, and a
function from that type to some other type, and combine them into a new parser which yields
values of the new type (&lt;code&gt;map&lt;&#x2F;code&gt;). To technically qualify as an applicative functor there would need
to be a way to take a value and build from it a parser that yields that value (&lt;code&gt;pure&lt;&#x2F;code&gt; in the literature),
but there doesn’t seem to be any point in actually including this function in meap.
The &lt;code&gt;apply&lt;&#x2F;code&gt; function that’s normally used to define applicative functors is left out as it can
be implement in terms of &lt;code&gt;pure&lt;&#x2F;code&gt;, &lt;code&gt;map&lt;&#x2F;code&gt;, and &lt;code&gt;both&lt;&#x2F;code&gt;, and is frankly less useful in this domain.&lt;&#x2F;p&gt;
&lt;p&gt;While the fact that an argument parser is an applicative functor is an interesting observation,
it’s of little practical use to anyone who just wants to parse arguments.
I expect may programmers have never encountered the term before, and
&lt;em&gt;I’d&lt;&#x2F;em&gt; sure feel uncomfortable using a library with an unfamiliar acronym in its name.&lt;&#x2F;p&gt;
&lt;p&gt;I also initially found it hilarious to say that my library was “Args AF!”, but it got old
pretty fast and some potential users may find it off-putting.&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Quality of life in command-line argument parsing</title>
          <pubDate>Sun, 13 Sep 2020 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/daily/quality-of-life-in-command-line-argument-parsing/</link>
          <guid>https://www.gridbugs.org/daily/quality-of-life-in-command-line-argument-parsing/</guid>
          <description xml:base="https://www.gridbugs.org/daily/quality-of-life-in-command-line-argument-parsing/">&lt;p&gt;I added a quality-of-life macro to &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;gridbugs&#x2F;args-af&quot;&gt;args_af&lt;&#x2F;a&gt;
to make it easier to specify arguments:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;struct &lt;&#x2F;span&gt;&lt;span&gt;Args {
&lt;&#x2F;span&gt;&lt;span&gt;    optional_int: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Option&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;i32&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;,
&lt;&#x2F;span&gt;&lt;span&gt;    string: String,
&lt;&#x2F;span&gt;&lt;span&gt;    flag: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;bool&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;impl &lt;&#x2F;span&gt;&lt;span&gt;Args {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;parse&lt;&#x2F;span&gt;&lt;span&gt;() -&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;Self &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;        (args_af::args_map&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;! &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;let &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;                optional_int &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;opt_opt&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;#39;i&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;);
&lt;&#x2F;span&gt;&lt;span&gt;                string &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;pos_req&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;STRING&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;);
&lt;&#x2F;span&gt;&lt;span&gt;                flag &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;flag&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;#39;f&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;);
&lt;&#x2F;span&gt;&lt;span&gt;            } &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;in &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;                &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;Self &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;                    optional_int,
&lt;&#x2F;span&gt;&lt;span&gt;                    string,
&lt;&#x2F;span&gt;&lt;span&gt;                    flag,
&lt;&#x2F;span&gt;&lt;span&gt;                }
&lt;&#x2F;span&gt;&lt;span&gt;            }
&lt;&#x2F;span&gt;&lt;span&gt;        })
&lt;&#x2F;span&gt;&lt;span&gt;        .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;parse_env&lt;&#x2F;span&gt;&lt;span&gt;()
&lt;&#x2F;span&gt;&lt;span&gt;        .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;unwrap&lt;&#x2F;span&gt;&lt;span&gt;()
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;…which generates code like:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;impl &lt;&#x2F;span&gt;&lt;span&gt;Args {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;parse&lt;&#x2F;span&gt;&lt;span&gt;() -&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;Self &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;opt_opt&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;#39;i&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;).&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;both&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;pos_req&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;STRING&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)).&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;both&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;flag&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;#39;f&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;))
&lt;&#x2F;span&gt;&lt;span&gt;        .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;map&lt;&#x2F;span&gt;&lt;span&gt;(|((optional_int, string), flag)&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;| &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;Self &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;                optional_int,
&lt;&#x2F;span&gt;&lt;span&gt;                string,
&lt;&#x2F;span&gt;&lt;span&gt;                flag,
&lt;&#x2F;span&gt;&lt;span&gt;            }
&lt;&#x2F;span&gt;&lt;span&gt;        })
&lt;&#x2F;span&gt;&lt;span&gt;        .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;parse_env&lt;&#x2F;span&gt;&lt;span&gt;()
&lt;&#x2F;span&gt;&lt;span&gt;        .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;unwrap&lt;&#x2F;span&gt;&lt;span&gt;()
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Note the chained calls to &lt;code&gt;both&lt;&#x2F;code&gt;, and the destructured nested pairs in the argument list
to the mapped function. The benefit of the &lt;code&gt;args_map&lt;&#x2F;code&gt; macro is it lets you associate the
specification of each argument with the variable it will be assigned to. Also it removes
the need to explicitly unpack the nested tuple created by repeated calls to &lt;code&gt;both&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Parsing my first command-line arguments</title>
          <pubDate>Sat, 12 Sep 2020 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/daily/parsing-my-first-command-line-arguments/</link>
          <guid>https://www.gridbugs.org/daily/parsing-my-first-command-line-arguments/</guid>
          <description xml:base="https://www.gridbugs.org/daily/parsing-my-first-command-line-arguments/">&lt;p&gt;Today I got &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;gridbugs&#x2F;args-af&quot;&gt;args_af&lt;&#x2F;a&gt; to the point that it can parse
command line arguments.
Here’s a simple program that parses arguments with my library:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;main&lt;&#x2F;span&gt;&lt;span&gt;() {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;use &lt;&#x2F;span&gt;&lt;span&gt;args_af::prelude::&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;*&lt;&#x2F;span&gt;&lt;span&gt;;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;let &lt;&#x2F;span&gt;&lt;span&gt;(foo, verbosity): (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;String&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;_&lt;&#x2F;span&gt;&lt;span&gt;) &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;opt_req&lt;&#x2F;span&gt;&lt;span&gt;()
&lt;&#x2F;span&gt;&lt;span&gt;        .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;long&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;foo&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)
&lt;&#x2F;span&gt;&lt;span&gt;        .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;both&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;flag_multi&lt;&#x2F;span&gt;&lt;span&gt;().&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;short&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;#39;v&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;).&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;long&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;verbose&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;))
&lt;&#x2F;span&gt;&lt;span&gt;        .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;parse_env&lt;&#x2F;span&gt;&lt;span&gt;()
&lt;&#x2F;span&gt;&lt;span&gt;        .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;unwrap&lt;&#x2F;span&gt;&lt;span&gt;();
&lt;&#x2F;span&gt;&lt;span&gt;    println!(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;{} {}&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;, foo, verbosity);
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Here’s how it looks in action:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;$ cargo run --example basic -- --foo=bar -vvvv --verbose
&lt;&#x2F;span&gt;&lt;span&gt;    Finished dev [unoptimized + debuginfo] target(s) in 0.01s
&lt;&#x2F;span&gt;&lt;span&gt;     Running `target&#x2F;debug&#x2F;examples&#x2F;basic --foo=bar -vvvv --verbose`
&lt;&#x2F;span&gt;&lt;span&gt;bar 5
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The next step is getting the library to generate help messages.&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>I was briefly wrong about list&#x27;s behaviour as an applicative</title>
          <pubDate>Fri, 11 Sep 2020 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/daily/i-was-briefly-wrong-about-list-s-behaviour-as-an-applicative/</link>
          <guid>https://www.gridbugs.org/daily/i-was-briefly-wrong-about-list-s-behaviour-as-an-applicative/</guid>
          <description xml:base="https://www.gridbugs.org/daily/i-was-briefly-wrong-about-list-s-behaviour-as-an-applicative/">&lt;p&gt;For today’s post I originally set out writing an explanation of the concept of &lt;em&gt;Applicative Functors&lt;&#x2F;em&gt;.
Partway through writing it, I was making an example involving lists, and it occurred to me that
contrary to my intuition up until that point, you cannot use &lt;code&gt;apply&lt;&#x2F;code&gt; to effectively &lt;code&gt;map&lt;&#x2F;code&gt; a function
of two arguments over a pair of lists, as one would typically do with &lt;code&gt;zipWith&lt;&#x2F;code&gt;.
If you try, your code will typecheck, but you’ll end up calling the function on all combinations of
values from the two lists, rather than just corresponding pairs. What’s weird is that for as long
as I’ve known that “lists are monads” I’ve internalised the fact that &lt;code&gt;bind&lt;&#x2F;code&gt;-ing on lists
enumerates all combinations of values, and it would be weird for &lt;code&gt;apply&lt;&#x2F;code&gt; and &lt;code&gt;bind&lt;&#x2F;code&gt; to behave differently.
And in hindsight it’s painfully obvious that an operation
as abstract as &lt;code&gt;apply&lt;&#x2F;code&gt; doesn’t care about such paltry details as the &lt;em&gt;length&lt;&#x2F;em&gt; of lists, as &lt;code&gt;zipWith&lt;&#x2F;code&gt;
does.&lt;&#x2F;p&gt;
&lt;p&gt;What gave it away was that I realised I expected &lt;code&gt;(pure (1+)) &amp;lt;*&amp;gt; [1, 2, 3]&lt;&#x2F;code&gt; to add 1 to each number in the RHS
(which it does) but I also expected &lt;code&gt;(pure (+)) &amp;lt;*&amp;gt; [1, 2, 3] &amp;lt;*&amp;gt; [4, 5, 6]&lt;&#x2F;code&gt; to be &lt;code&gt;[5, 7, 9]&lt;&#x2F;code&gt;.
I wrote down as much, and this set off all sorts of internal consistency checks in my head because
these two things can’t be true at the same time. That’s the whole point of doing these daily posts.
It seems like it’s easier to be wrong in your head than on paper.&lt;&#x2F;p&gt;
&lt;p&gt;And if anyone is reading this and wondering about the results of the expressions above:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;haskell&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-haskell &quot;&gt;&lt;code class=&quot;language-haskell&quot; data-lang=&quot;haskell&quot;&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Prelude&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt; (pure (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;+&lt;&#x2F;span&gt;&lt;span&gt;)) &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span&gt;*&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt; [&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;2&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;3&lt;&#x2F;span&gt;&lt;span&gt;]
&lt;&#x2F;span&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;2&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;3&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;4&lt;&#x2F;span&gt;&lt;span&gt;]
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Prelude&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt; (pure &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;(+)&lt;&#x2F;span&gt;&lt;span&gt;) &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span&gt;*&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt; [&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;2&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;3&lt;&#x2F;span&gt;&lt;span&gt;] &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span&gt;*&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt; [&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;4&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;5&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;6&lt;&#x2F;span&gt;&lt;span&gt;]
&lt;&#x2F;span&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;5&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;6&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;7&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;6&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;7&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;8&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;7&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;8&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;9&lt;&#x2F;span&gt;&lt;span&gt;]
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Now go read &lt;a href=&quot;http:&#x2F;&#x2F;learnyouahaskell.com&#x2F;functors-applicative-functors-and-monoids#applicative-functors&quot;&gt;learnyouahaskell&lt;&#x2F;a&gt;!&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Low-Level and High-Level Command-Line Argument Parsing</title>
          <pubDate>Thu, 10 Sep 2020 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/daily/low-level-and-high-level-command-line-argument-parsing/</link>
          <guid>https://www.gridbugs.org/daily/low-level-and-high-level-command-line-argument-parsing/</guid>
          <description xml:base="https://www.gridbugs.org/daily/low-level-and-high-level-command-line-argument-parsing/">&lt;p&gt;A natural way to approach the problem of command-line argument parsing is to split it into two parts.&lt;&#x2F;p&gt;
&lt;p&gt;Start with a simple parser that takes the raw sequence of strings given to a
program when it starts (its arguments), along with a description of what which arguments to look
for, and produces a structure which can answer queries of the form “How many times was the flag &lt;code&gt;-v&lt;&#x2F;code&gt;
passed to the program?”, or “List all the strings that immediately succeed the flag &lt;code&gt;--input&lt;&#x2F;code&gt;”.
If you want your command-line argument parser to treat certain patterns specially, such as interpreting
&lt;code&gt;-abc&lt;&#x2F;code&gt; as &lt;code&gt;-a -b -c&lt;&#x2F;code&gt;, or treat everything after the first &lt;code&gt;--&lt;&#x2F;code&gt; as literal arguments (not flags), now
is the time to implement that behaviour. At this level the only types are strings, and no arity
rules are considered (e.g optional vs required vs variadic arguments).&lt;&#x2F;p&gt;
&lt;p&gt;The low level parser faces the raw argument list, and the high level parser faces the humans who use
and program the application whose arguments are being parsed. A programmer should be able to specify
the program’s arguments declaratively, including arity and descriptions,
and treat arguments as if they have types besides string.
Users should be able to print a message describing the arguments to the program (e.g. when running
the program with &lt;code&gt;--help&lt;&#x2F;code&gt;). This help text should be generated automatically from the declarative
command-line argument spec.&lt;&#x2F;p&gt;
&lt;p&gt;When the parser runs, the high level parser will configure a low level parser to look for particular
arguments in the raw argument list. The low level parser then runs, and produces a queriable structure.
The high level parser then queries this structure to bring into being the arguments described in
the declarative spec.&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>New argument parsing library</title>
          <pubDate>Wed, 09 Sep 2020 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/daily/new-argument-parsing-library/</link>
          <guid>https://www.gridbugs.org/daily/new-argument-parsing-library/</guid>
          <description xml:base="https://www.gridbugs.org/daily/new-argument-parsing-library/">&lt;p&gt;A few years ago I made &lt;a href=&quot;https:&#x2F;&#x2F;crates.io&#x2F;crates&#x2F;simon&quot;&gt;simon&lt;&#x2F;a&gt; - a rust library
for parsing command-line arguments. Looking at it with fresh eyes, I’ve decided
that it’s over-engineered, and places too much emphasis on generality and
theoretical niceness rather than being useful.&lt;&#x2F;p&gt;
&lt;p&gt;Today I started working on &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;gridbugs&#x2F;args-af&quot;&gt;args_af&lt;&#x2F;a&gt;
which is my attempt at making a minimal, pragmatic command-line parser.
The biggest difference from simon is combinators only work on specific
parsers, rather than generalising to all parsers. It’s done this way to
simplify generating help messages. Like simon, it uses the idea of
“Applicative Functors” (hence the “af”), but it’s less in-your-face
about it, and only really uses them to combine a collection of parsers
into a single parser of a collection of values.&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Re-added MBR parsing to my GPT disk image library</title>
          <pubDate>Tue, 08 Sep 2020 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/daily/re-added-mbr-parsing-to-my-gpt-disk-image-library/</link>
          <guid>https://www.gridbugs.org/daily/re-added-mbr-parsing-to-my-gpt-disk-image-library/</guid>
          <description xml:base="https://www.gridbugs.org/daily/re-added-mbr-parsing-to-my-gpt-disk-image-library/">&lt;p&gt;When I first started working on &lt;a href=&quot;https:&#x2F;&#x2F;crates.io&#x2F;crates&#x2F;mini_gpt&quot;&gt;mini_gpt&lt;&#x2F;a&gt;,
the first thing I made was a parser for the disk image’s Master Boot Record (MBR).
This felt natural, as the MBR is the first piece of data stored on a hard drive,
regardless of whether it has a GPT partition table. But decoding the MBR isn’t
necessary to determine the byte range of the first partition on a GPT disk, which
at the time of writing is the only function exposed by mini_gpt.
Thus, in the interest of aggressive minimalism, I removed MBR parsing from the
library.&lt;&#x2F;p&gt;
&lt;p&gt;That is until today, when I added it back. The next piece of functionality I’ll
be adding to the library will be generating disk images, and generated disk
images must include an MBR. It will also be useful to check the validity of
disk images by reading their MBR and asserting it is sane.&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>First thoughts after playing Epistory</title>
          <pubDate>Mon, 07 Sep 2020 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/daily/first-thoughts-after-playing-epistory/</link>
          <guid>https://www.gridbugs.org/daily/first-thoughts-after-playing-epistory/</guid>
          <description xml:base="https://www.gridbugs.org/daily/first-thoughts-after-playing-epistory/">&lt;p&gt;Last weekend I played the first chapter of &lt;a href=&quot;http:&#x2F;&#x2F;www.epistorygame.com&#x2F;&quot;&gt;Epistory - Typing Chronicles&lt;&#x2F;a&gt;.
It’s light RPG where the primary form of interaction is typing words.
For example to open a chest you type several randomly chosen words which appear above it.
I started playing to improve my qwerty typing, but I’ll continue playing because I find the
premise far more engaging than I expected.&lt;&#x2F;p&gt;
&lt;p&gt;I never payed much attention to typing games. When first learning to type I played some educational
games which featured typing mechanics as a way to teach you to type.
Epistory is not educational, and instead uses typing mechanics to enhance gameplay and narrative.&lt;&#x2F;p&gt;
&lt;p&gt;In order to survive a combat encounter, you must rapidly type a specified sequence of words
for each of the numerous enemies charging toward the player character.
The player must accurately and quickly perform a task requiring manual dexterity (ie. typing
on a keyboard) in order for their avatar to perform dexterity-based
combat maneuvers, thus aligning the experience of player and character.&lt;&#x2F;p&gt;
&lt;p&gt;The “literal” narrative of Epistory (from what I’ve seen so far) is about restoring
life to a corrupted woods, which is a fairly standard RPG plot. This is just
a medium for the actual plot, told sparingly in tidbits of flavour text
and the occasional voice over. The words you type to interact with the world are
randomly chosen, and don’t comprise meaningful sentences, but the pool from which the words
are chosen varies throughout the game, and depending on the situation, often serving
to reinforce the tone of the narrative.&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Word navigation in Vim</title>
          <pubDate>Sun, 06 Sep 2020 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/daily/word-navigation-in-vim/</link>
          <guid>https://www.gridbugs.org/daily/word-navigation-in-vim/</guid>
          <description xml:base="https://www.gridbugs.org/daily/word-navigation-in-vim/">&lt;p&gt;Since I’m retraining my hands to type in qwerty rather than dvorak, I figure I’ll
use this as a chance to kick some bad habits in Vim - my text editor of choice.
Today’s lesson is “word navigation”.&lt;&#x2F;p&gt;
&lt;p&gt;Vim gives you several different ways to move the cursor.
Word navigation lets you skip to the start and ends of words.
The relevant keys (in normal mode):&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;w&lt;&#x2F;code&gt; moves the cursor to the start of the next word after the cursor&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;e&lt;&#x2F;code&gt; moves the cursor to the next word-end after the cursor&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;b&lt;&#x2F;code&gt; moves the cursor to the previous word-start before the cursor&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;The capital-letter versions of these commands do the same thing, except treat
whitespace as the only word separator rather than all punctuation.&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Reading and listing files with my disk image tools</title>
          <pubDate>Sat, 05 Sep 2020 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/daily/reading-and-listing-files-with-my-disk-image-tools/</link>
          <guid>https://www.gridbugs.org/daily/reading-and-listing-files-with-my-disk-image-tools/</guid>
          <description xml:base="https://www.gridbugs.org/daily/reading-and-listing-files-with-my-disk-image-tools/">&lt;p&gt;Today I completed the tools for reading and listing files from a gpt&#x2F;fat disk image.
The souce code is &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;gridbugs&#x2F;gpt-fat-disk-image&quot;&gt;here&lt;&#x2F;a&gt;.
Next I need to make a tool that generates gpt&#x2F;fat disk images containing a specified
collection of files.&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Qwerty Quirks</title>
          <pubDate>Fri, 04 Sep 2020 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/daily/qwerty-quirks/</link>
          <guid>https://www.gridbugs.org/daily/qwerty-quirks/</guid>
          <description xml:base="https://www.gridbugs.org/daily/qwerty-quirks/">&lt;p&gt;Here are two complications of switching to qwerty that I’m dealing with.&lt;&#x2F;p&gt;
&lt;p&gt;In vim, and many other tools, the HJKL keys are used for navigation in lieu of (or as well as) arrow keys.
To use vim with dvorak, these keys must be remapped. When setting this up, I elected to use the JKL; keys
instead, as this are where your right hand normally rests while typing. In qwerty I want to use the default
navigation keys again, which are one space to the left of where I’m used to placing my hands.
Navigation keys work in “normal” mode, and the “I” key is used to enter “insert” mode. Since “I” is above
HJKL, I use my right hand to press “I” as well as HJKL. After entering insert mode, I move my hand back
to the JKL; keys. Relative to the placement of my right hand, the “I” key is in a different place depending
on whether I’m in insert or normal mode.&lt;&#x2F;p&gt;
&lt;p&gt;The second problem is in tmux. I used to use ctrl-u as the prefix, which in dvorak means I was pressing
the physical f key. I can’t change the prefix to ctrl-f as I use ctrl-f to search files in vim.
The default prefix is ctrl-b, but “B” is so far from the control key that I have to press “B” with my
right hand (and control with my left). I’ve changed the prefix to ctrl-x instead, as that has no use
to me in other terminal-bound programs. Since the tmux prefix is something I use all the time, I frequently
find myself pressing ctrl-f instead.&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Relearning Qwerty</title>
          <pubDate>Thu, 03 Sep 2020 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/daily/relearning-qwerty/</link>
          <guid>https://www.gridbugs.org/daily/relearning-qwerty/</guid>
          <description xml:base="https://www.gridbugs.org/daily/relearning-qwerty/">&lt;p&gt;For the past 10 years I’ve used a dvorak keyboard, but I’m currently trying to switch
back to qwerty. I switched to dvorak on a whim. I’m switching back to simplify my
software configuration, in particular, to remove the need to rebind vi-keys in all applications that
support them. It’s not clear which keymap is superior. I have a sense that typing
on dvorak felt “smoother”, but it’s hard to be scientific about such evaluations.&lt;&#x2F;p&gt;
&lt;p&gt;One day in to using qwerty again, and thus far the experience has been humbling and
extremely frustrating!&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Disk image tool no longer reads entire image</title>
          <pubDate>Wed, 02 Sep 2020 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/daily/disk-image-tool-no-longer-reads-entire-image/</link>
          <guid>https://www.gridbugs.org/daily/disk-image-tool-no-longer-reads-entire-image/</guid>
          <description xml:base="https://www.gridbugs.org/daily/disk-image-tool-no-longer-reads-entire-image/">&lt;p&gt;In the original version of &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;gridbugs&#x2F;gpt-fat-disk-image&quot;&gt;my disk image tool&lt;&#x2F;a&gt;
I read the entire image file into memory once before processing the image. This was convenient
but for large files it requires a lot of memory and took several seconds. Now I dynamically
seeks the image file and only reads what it needs to. I also deleted a bunch of code in an
attempt to keep things as simple as possible. Currently the tool just lists the files in the
root directory. Next step is to list the files at a specified directory.&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Disk image tool progress</title>
          <pubDate>Tue, 01 Sep 2020 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/daily/disk-image-tool-progress/</link>
          <guid>https://www.gridbugs.org/daily/disk-image-tool-progress/</guid>
          <description xml:base="https://www.gridbugs.org/daily/disk-image-tool-progress/">&lt;p&gt;I’m making steady progress on my &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;gridbugs&#x2F;gpt-fat-disk-image&quot;&gt;gpt fat disk image tools&lt;&#x2F;a&gt; project.
As of tonight I can list the “long names” of files in the root directory of the first partition on the disk image.
The first milestone will be when I have a tool that can list the files under a given path rather than just the root.
The next step will be removing the need to load the entire image into a huge buffer before processing it.&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Creating and mounting a FAT32 partition in a GPT disk on FreeBSD</title>
          <pubDate>Mon, 31 Aug 2020 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/daily/creating-and-mounting-a-fat32-partition-in-a-gpt-disk-on-freebsd/</link>
          <guid>https://www.gridbugs.org/daily/creating-and-mounting-a-fat32-partition-in-a-gpt-disk-on-freebsd/</guid>
          <description xml:base="https://www.gridbugs.org/daily/creating-and-mounting-a-fat32-partition-in-a-gpt-disk-on-freebsd/">&lt;p&gt;To test my disk image tool, I set up a spare USB stick with a GPT partition
table and FAT32 partition. Doing this on FreeBSD meant learning about a couple of
new tools. All the necessary tools are part of the base system, so no installation
is necessary. This was written based on FreeBSD 12.1.&lt;&#x2F;p&gt;
&lt;p&gt;Plug in a USB hard drive, and watch the output of &lt;code&gt;dmesg&lt;&#x2F;code&gt; to determine its name.
In my case the drive was named &lt;code&gt;&#x2F;dev&#x2F;da1&lt;&#x2F;code&gt;. The following commands will destroy anything
previously on that drive. Double check that the file passed to these commands is
corresponds to the disk you plugged in!&lt;&#x2F;p&gt;
&lt;p&gt;These commands require root.&lt;&#x2F;p&gt;
&lt;p&gt;Clear any existing partition table.&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;# gpart destroy -F &#x2F;dev&#x2F;da1
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Make a new GPT partition table.&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;gpart create -s GPT &#x2F;dev&#x2F;da1
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Make a new 200mb partition of type “efi”.
The new partition will have a device node &lt;code&gt;&#x2F;dev&#x2F;da1p1&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;# gpart add -s 200M -t efi &#x2F;dev&#x2F;da1
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Format the new partition with the FAT32 filesystem.&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;# newfs_msdos -F 32 -c 1 &#x2F;dev&#x2F;da1p1
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Mount the partition, at &lt;code&gt;&#x2F;mnt&lt;&#x2F;code&gt; allowing user 1001 (me) access.&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;# mount_msdosfs -u 1001 &#x2F;dev&#x2F;da1p1 &#x2F;mnt&#x2F;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;And then as myself (uid 1001), create a file on the disk.&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;$ echo &amp;quot;Hello, World!&amp;quot; &amp;gt; &#x2F;mnt&#x2F;hello.txt
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Finally, to make a disk image for the purpose of testing the tool, run (as root):&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;# dd if=&#x2F;dev&#x2F;da1 of=&#x2F;tmp&#x2F;test.img bs=1m
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Beware that the resulting file (&lt;code&gt;&#x2F;tmp&#x2F;test.img&lt;&#x2F;code&gt;) will be the size of the hard drive!&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Image Creation Tool - First Steps</title>
          <pubDate>Sun, 30 Aug 2020 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/daily/image-creation-tool-first-steps/</link>
          <guid>https://www.gridbugs.org/daily/image-creation-tool-first-steps/</guid>
          <description xml:base="https://www.gridbugs.org/daily/image-creation-tool-first-steps/">&lt;p&gt;I’ve started working on a new personal project: &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;gridbugs&#x2F;gpt-fat-disk-image&quot;&gt;GPT FAT Disk Image Tools&lt;&#x2F;a&gt;.
That’s a boring name for a boring (in a good way!) piece of software.
It’s going to be a modest collection of simple tools for working with images of hard drives
containing &lt;a href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;GUID_Partition_Table&quot;&gt;GPT Partition Tables&lt;&#x2F;a&gt;
where the first partition is formatted with a &lt;a href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;File_Allocation_Table&quot;&gt;FAT Filesystem&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;The problem it solves is the following. When doing x86 OS development, I frequently want to run my code in
an emulator&#x2F;VM such as qemu. In order to boot an operating system on an emulator, one must present a
disk image for the emulator to boot from.
Modern PCs use a firmware interface named &lt;a href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;Unified_Extensible_Firmware_Interface&quot;&gt;UEFI&lt;&#x2F;a&gt;.
Compared to its predecessor &lt;a href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;BIOS&quot;&gt;BIOS&lt;&#x2F;a&gt;, it presents a higher-level of abstraction
to boot code, and rather than loading and executing code from the
&lt;a href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;Master_boot_record&quot;&gt;Master Boot Record&lt;&#x2F;a&gt;
of your hard drive, it loads a file from one of a handful of pre-defined locations (such as &lt;code&gt;&#x2F;EFI&#x2F;BOOT&#x2F;BOOTX64.EFI&lt;&#x2F;code&gt;)
from the first partition of the hard drive. The hard drive must have a GPT partition table, and its first partition
must be formatted with a FAT filesystem (there appears to be some flexibility as to &lt;em&gt;which&lt;&#x2F;em&gt; FAT is used).&lt;&#x2F;p&gt;
&lt;p&gt;The process of creating a disk image containing a GPT partition table with a FAT-formatted partition
is harder than it needs to be.
The &lt;a href=&quot;https:&#x2F;&#x2F;wiki.osdev.org&#x2F;UEFI&quot;&gt;osdev wiki page on UEFI&lt;&#x2F;a&gt;
gives a list of ways to create a UEFI-compatible disk image,
organized by OS and whether or not they require root.
I want my hobby OS to have a &lt;code&gt;Makefile&lt;&#x2F;code&gt; rule than builds a bootable disk image,
and running build tools as root is dangerous,
and there’s no reason creating or formatting a disk image should require root.
I frequently bounce between linux and freebsd, so I need a platform-independent solution.&lt;&#x2F;p&gt;
&lt;p&gt;GPT and FAT are both fairly simple and well-documented standards.
So far I can decode GPT and FAT headers, and list the files in the root directory of of FAT12 and FAT16 partitions.&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>My keyboard backlight works!</title>
          <pubDate>Sat, 29 Aug 2020 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/daily/my-keyboard-backlight-works/</link>
          <guid>https://www.gridbugs.org/daily/my-keyboard-backlight-works/</guid>
          <description xml:base="https://www.gridbugs.org/daily/my-keyboard-backlight-works/">&lt;p&gt;About a month ago I switch to FreeBSD on my laptop - a Lenovo Thinkpad T470.
After an evening of messing around, I managed to get all the hardware working
with the exception of the keyboard backlight. I resigned myself to live without
this feature if necessary, and figured I could always add support for it myself.
It works fine on linux, so the driver is likely open source and I can use it
as a reference.&lt;&#x2F;p&gt;
&lt;p&gt;Well it turns out that won’t be necessary. A quick search for “freebsd thinkpad keyboard backlight”
yielded &lt;a href=&quot;https:&#x2F;&#x2F;support.lenovo.com&#x2F;au&#x2F;en&#x2F;solutions&#x2F;ht104451&quot;&gt;this lenovo support page&lt;&#x2F;a&gt;
as the first result, which has nothing to do with FreeBSD, but did solve my problem.
Fn+space toggles the keyboard backlight on thinkpads, and it seems to be supported out of the box
(or possibly due to enabling the &lt;code&gt;acpi_ibm&lt;&#x2F;code&gt; module).&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;daily&#x2F;my-keyboard-backlight-works&#x2F;key.jpg&quot; alt=&quot;key.jpg&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;So &lt;em&gt;that’s&lt;&#x2F;em&gt; what that symbol means.&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Planning to make a simple disk image creator</title>
          <pubDate>Fri, 28 Aug 2020 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/daily/planning-to-make-a-simple-disk-image-creator/</link>
          <guid>https://www.gridbugs.org/daily/planning-to-make-a-simple-disk-image-creator/</guid>
          <description xml:base="https://www.gridbugs.org/daily/planning-to-make-a-simple-disk-image-creator/">&lt;p&gt;Several times in the past few weeks I’ve been frustrated by the lack of a tool
for generating a disk image containing a partition with a filesystem containing
a set of given files. Such a tool would be very useful in OS development.
Bootboot’s &lt;code&gt;mkbootimg&lt;&#x2F;code&gt; tool, and grubs &lt;code&gt;grub-mkrescue&lt;&#x2F;code&gt; do something similar to
what I want, but both with some pretty major caveats, and  there doesn’t seem to be a
standard, general purpose, platform independent way of doing this.
I want to spend some time this weekend working out whether it would be feasible
to make this tool myself, targeting GPT partition tables, and the FAT32 filesystem,
as that’s what’s required to make a disk image that’s bootable by a UEFI system,
which is the use case I have in mind.&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Should I make my own bootloader?</title>
          <pubDate>Thu, 27 Aug 2020 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/daily/should-i-make-my-own-bootloader/</link>
          <guid>https://www.gridbugs.org/daily/should-i-make-my-own-bootloader/</guid>
          <description xml:base="https://www.gridbugs.org/daily/should-i-make-my-own-bootloader/">&lt;p&gt;On the road to setting up basic memory-management for my hobby OS, I found that
the bootloader I’ve elected to use (&lt;a href=&quot;https:&#x2F;&#x2F;gitlab.com&#x2F;bztsrc&#x2F;bootboot&quot;&gt;bootboot&lt;&#x2F;a&gt;)
doesn’t tell the booting kernel about all the available physical memory. In particular
it omits the memory regions which it uses to store the initial paging hierarchy which
it sets up before starting the kernel. Looking at its source code, this appears to
be by design, though I don’t understand why it’s necessary.&lt;&#x2F;p&gt;
&lt;p&gt;I want my project to be easy to build on at least linux and freebsd. With the
exception of the &lt;code&gt;mkbootimg&lt;&#x2F;code&gt; tool packaged with bootboot (for generating bootable disk images), the only packages required
to build the OS are GNU binutils, the netwide assembler (though I’m planning on switching
to GNU assembler), and qemu (if you want to run the OS in an emulator&#x2F;VM).&lt;&#x2F;p&gt;
&lt;p&gt;Dropping bootboot would mean losing &lt;code&gt;mkbootimg&lt;&#x2F;code&gt; (unless I implemented a bootboot-compliant bootloader
(bootboot is a &lt;em&gt;protocol&lt;&#x2F;em&gt; as well as a reference implementation)). The specific problem which &lt;code&gt;mkbootimg&lt;&#x2F;code&gt;
solves is taking a binary file containing a compiled kernel (an ELF file) as input, and producing as output
a hard disk image with a GPT partition table, whose first partition is FAT32 formatted,
and inside that partition resides a bootloader UEFI file (understood by the system’s UEFI firmware),
and the kernel ELF. Every alternative tool I’ve found for doing this is specific to linux.
The &lt;code&gt;grub&lt;&#x2F;code&gt; bootloader comes with a tool &lt;code&gt;grub-mkrescue&lt;&#x2F;code&gt; which is a popular way to make bootable disk images
for hobby OS projects, but the grub no longer exists in freebsd. You can also make bootable disk image by
&lt;code&gt;fdisk&lt;&#x2F;code&gt;-ing a regular file, &lt;code&gt;mkvs.vfat&lt;&#x2F;code&gt;-ing a partition onto it, and mounting the partition to copy
the relevant files to it, but this again is different between linux and bsd, and worse - it requires running
commands as root (mounting&#x2F;unmounting the partition from the disk image). A sorry state of affairs indeed!&lt;&#x2F;p&gt;
&lt;p&gt;Finally, the whole point of this project was for me to learn more about systems programming,
and a lot of the messy details of booting on x86 are taken care of by bootboot.&lt;&#x2F;p&gt;
&lt;p&gt;So I’m probably going to make a minimal x86 UEFI bootloader and disk image creator,
using bootboot and &lt;code&gt;mkbootimg&lt;&#x2F;code&gt; as a reference.&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Fixing bugs in sample code</title>
          <pubDate>Wed, 26 Aug 2020 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/daily/fixing-bugs-in-sample-code/</link>
          <guid>https://www.gridbugs.org/daily/fixing-bugs-in-sample-code/</guid>
          <description xml:base="https://www.gridbugs.org/daily/fixing-bugs-in-sample-code/">&lt;p&gt;In the last day I’ve found bugs in two different kernel code samples I’ve been using
as a reference for my own hobby OS. I’ve done by best to correct them.&lt;&#x2F;p&gt;
&lt;p&gt;The first was in &lt;a href=&quot;https:&#x2F;&#x2F;blog.llandsmeer.com&#x2F;tech&#x2F;2019&#x2F;07&#x2F;21&#x2F;uefi-x64-userland.html&quot;&gt;this blog post&lt;&#x2F;a&gt;
which contains (among other things) an example of how to set up a Global Descriptor Table (GDT)
and Task State Segment (TSS), but it mixed up the code and data segments when installing the GDT,
and forgot to set the most-significant 16 bits of the TSS address in its GDT descriptor.
I’ve emailed the author with details.&lt;&#x2F;p&gt;
&lt;p&gt;Second issue was in &lt;a href=&quot;https:&#x2F;&#x2F;gitlab.com&#x2F;bztsrc&#x2F;bootboot&quot;&gt;bootboot&lt;&#x2F;a&gt; which is the bootloader
I’m using to boot my hobby OS. It includes a sample “hello world” c kernel which declares a
&lt;code&gt;extern char* environment&lt;&#x2F;code&gt; pointing to a string containing kernel arguments, whose &lt;em&gt;address&lt;&#x2F;em&gt; is specified
in a linker script, but treats &lt;code&gt;environment&lt;&#x2F;code&gt; as if its &lt;em&gt;value&lt;&#x2F;em&gt; was the address of the argument string
(it should have been a &lt;code&gt;char&lt;&#x2F;code&gt; array). I made a pull request to address this.&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Quick&#x27;n&#x27;dirty user-level switching</title>
          <pubDate>Tue, 25 Aug 2020 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/daily/quick-n-dirty-user-level-switching/</link>
          <guid>https://www.gridbugs.org/daily/quick-n-dirty-user-level-switching/</guid>
          <description xml:base="https://www.gridbugs.org/daily/quick-n-dirty-user-level-switching/">&lt;p&gt;My goal for the last couple of days has been getting my hobby OS project to the point where
it can switch into user-mode. I was technically at that point yesterday, but with an
unsatisfying caveat: the instant the processor entered user-mode (by means of the &lt;code&gt;sysret&lt;&#x2F;code&gt; instruction)
it would page fault, and for a reasons I still don’t understand, my interrupt handler wasn’t invoked.&lt;&#x2F;p&gt;
&lt;p&gt;Today I managed to get user-level to (deliberately) infinitely loop. I can use a debugger to verify
that the processor is in-fact in user-mode. To prevent the fault upon entering user-mode, I adjusted
some access flags in the paging hierarchy to permit the processor to access the memory containing
the user program and stack, while the processor is in user-mode.&lt;&#x2F;p&gt;
&lt;p&gt;I was wrong about the bootloader’s ELF-loading. I incorrectly assumed that it would set up paging
to match the virtual addresses specified in an ELF-file, but it only loads code&#x2F;data into a specific
region of virtual memory, and sets up paging for that region. This means in order to have the user
program be loaded at its expected virtual address, I need to identify unused physical memory
and map some of it at this address.&lt;&#x2F;p&gt;
&lt;p&gt;In the interest of just getting something simple working, I’m currently setting access bits
such that user-mode has access to some code from the kernel, and the kernel’s stack.
That way I can just write a &lt;code&gt;while(1);&lt;&#x2F;code&gt; function in the kernel code, and run the &lt;code&gt;sysret&lt;&#x2F;code&gt; instruction
such that it “returns” to user-mode at the beginning of this function.&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>x86 Basic Interrupt Handling Kind of Working</title>
          <pubDate>Mon, 24 Aug 2020 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/daily/x86-basic-interrupt-handling-kind-of-working/</link>
          <guid>https://www.gridbugs.org/daily/x86-basic-interrupt-handling-kind-of-working/</guid>
          <description xml:base="https://www.gridbugs.org/daily/x86-basic-interrupt-handling-kind-of-working/">&lt;p&gt;My hobby OS adventure has led to the need to handle interrupts.
In x86, you set up a data structure called an “Interrupt Descriptor Table” (IDT)
which tells the hardware which function to call in response to different interrupts.
My setup kind of works; I can trigger an interrupt with the &lt;code&gt;int&lt;&#x2F;code&gt; instruction, or
cause a page fault in kernel mode by accessing unmapped memory, and see that my interrupt handler is invoked.
For some reason immediately after switching to user mode a page fault is generated (unsurprising as nothing is mapped user-accessible yet),
but my handler is &lt;em&gt;not&lt;&#x2F;em&gt; invoked. No idea why this could be!&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Global Descriptor Table</title>
          <pubDate>Sun, 23 Aug 2020 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/daily/global-descriptor-table/</link>
          <guid>https://www.gridbugs.org/daily/global-descriptor-table/</guid>
          <description xml:base="https://www.gridbugs.org/daily/global-descriptor-table/">&lt;p&gt;In x86 processors, the &lt;a href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;Global_Descriptor_Table&quot;&gt;Global Descriptor Table&lt;&#x2F;a&gt;
is a data structure that stores characteristics of regions of memory known as “segments”.
Example characteristics are the start address of the segment, the size of the segment, and access flags,
such as whether the segment is writable or executable.
Back in the days of 16-bit addresses, the ability to dynamically switch the current segment
allowed programmers to effectively address more 64k.&lt;&#x2F;p&gt;
&lt;p&gt;Nowadays, in 64-bit mode, a Global Descriptor Table must still be set up, largely for legacy reasons it seems.
Certain instructions, notably those which move control between distant virtual address (such as returning
from a system call), take a segment index as an argument, and change the current &lt;em&gt;segment&lt;&#x2F;em&gt; in addition
to the current &lt;em&gt;instruction pointer&lt;&#x2F;em&gt; address.&lt;&#x2F;p&gt;
&lt;p&gt;Typically the GDT has at least 2 entries (in addition to the
mandatory null entry at index 0) - one for code, and a second for data. I’m not sure whether it’s necessary
to have one such pair for the kernel, and a second pair of segments for user-mode. My current goal is to get
the kernel-to-user-mode switch to happen &lt;em&gt;at all&lt;&#x2F;em&gt;, then go over everything I’m doing and make sure I’m doing
it “right”. Once I have a basic user-mode “thread” running, I’ll do a longer write-up explaining how to get
from zero to this point.&lt;&#x2F;p&gt;
&lt;p&gt;A page that turned out to be of great practical use:
&lt;a href=&quot;https:&#x2F;&#x2F;blog.llandsmeer.com&#x2F;tech&#x2F;2019&#x2F;07&#x2F;21&#x2F;uefi-x64-userland.html&quot;&gt;Builing a UEFI x64 kernel from scratch: A long trip to userspace&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Trampolining</title>
          <pubDate>Sat, 22 Aug 2020 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/daily/trampolining/</link>
          <guid>https://www.gridbugs.org/daily/trampolining/</guid>
          <description xml:base="https://www.gridbugs.org/daily/trampolining/">&lt;p&gt;Today I solved an os-dev problem I’d been stuck on for a few days.
In my hobby OS project, I want the bootable image to contain both the kernel code, and the code of the
initial user-level application, packed into a single ELF file, which the bootloader loads before
starting the kernel. In order for the kernel to (eventually) hand control over to the user program,
it needs to know the user program’s entry point. I have linker script that describes the (virtual)
address space layout of the user and kernel memory, which the bootloader instantiates.
My original plan was to use the linker to make a symbol available in the kernel code which refers
to the entry point of the user program (ie. it’s &lt;code&gt;_start&lt;&#x2F;code&gt; function’s address), but since user and
kernel virtual addresses are conventionally very far apart, this turned out to be non-trivial.&lt;&#x2F;p&gt;
&lt;p&gt;I think I have a solution, though I’m yet to fully test it out (some other things have got in the way).
The plan is the following: Add a new section to the ELF with a tiny bit of code at the start that just
jumps to the user program’s entry point. Something like&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;asm&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-asm &quot;&gt;&lt;code class=&quot;language-asm&quot; data-lang=&quot;asm&quot;&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;; the user &amp;quot;_start&amp;quot; function is renamed to &amp;quot;__user__start&amp;quot; to avoid name conflicts with kernel code
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;extern &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;__user__start
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;bits 64
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;section &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;.user.trampoline
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;jmp &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;__user__start
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This solves the problem because unlike the user’s &lt;code&gt;_start&lt;&#x2F;code&gt; function, which could be located anywhere
within the user program’s &lt;code&gt;.text&lt;&#x2F;code&gt; section, we can tell the linker to put the above code at the beginning
of a brand new section dedicated just for it:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;ld&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-ld &quot;&gt;&lt;code class=&quot;language-ld&quot; data-lang=&quot;ld&quot;&gt;&lt;span&gt;. = user_base;
&lt;&#x2F;span&gt;&lt;span&gt;.user.trampoline : &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;ALIGN&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;0x1000&lt;&#x2F;span&gt;&lt;span&gt;) {
&lt;&#x2F;span&gt;&lt;span&gt;    *(.user.trampoline)
&lt;&#x2F;span&gt;&lt;span&gt;} :user
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This introduces a &lt;code&gt;user_base&lt;&#x2F;code&gt; symbol which can be &lt;code&gt;extern&lt;&#x2F;code&gt;-ed in kernel code, so when the kernel is
ready, it can switch to user mode and move execution to the address referred to by the symbol &lt;code&gt;user_base&lt;&#x2F;code&gt;.
Execution will then &lt;em&gt;bounce&lt;&#x2F;em&gt; to the actual user entry point by means of the &lt;code&gt;jmp __user__start&lt;&#x2F;code&gt; instruction.&lt;&#x2F;p&gt;
&lt;p&gt;This technique is known as “trampolining”.&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>More fun with linking</title>
          <pubDate>Fri, 21 Aug 2020 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/daily/more-fun-with-linking/</link>
          <guid>https://www.gridbugs.org/daily/more-fun-with-linking/</guid>
          <description xml:base="https://www.gridbugs.org/daily/more-fun-with-linking/">&lt;p&gt;I managed to resolve my problem from &lt;a href=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;daily&#x2F;i-need-to-go-learn-more-about-elf-and-static-linking&#x2F;&quot;&gt;yesterday&lt;&#x2F;a&gt;, though I’m still not
satisfied that I understand why it &lt;em&gt;now&lt;&#x2F;em&gt; works, so it’s possible that it just works by accident.
Evidently there’s still a lot I need to learn about linking.&lt;&#x2F;p&gt;
&lt;p&gt;A new problem I ran into is working out the entry point of the user program from the kernel linked
into the same ELF (binary).
This is necessary for the kernel to start a user-level thread, whose initial program counter will
be this entry point.
My original plan was to use the &lt;code&gt;_start&lt;&#x2F;code&gt; symbol from the user program
(renamed using objcopy), which after relocation will contain the address I want.
This doesn’t appear to “just work” the way I expected, so there’s clearly even more I still need
to learn about linking!&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>I need to go learn more about ELF and static linking</title>
          <pubDate>Thu, 20 Aug 2020 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/daily/i-need-to-go-learn-more-about-elf-and-static-linking/</link>
          <guid>https://www.gridbugs.org/daily/i-need-to-go-learn-more-about-elf-and-static-linking/</guid>
          <description xml:base="https://www.gridbugs.org/daily/i-need-to-go-learn-more-about-elf-and-static-linking/">&lt;p&gt;I’m working out the shortest path from a
&lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;gridbugs&#x2F;hello-kernel&quot;&gt;kernel-mode program that prints “Hello, World!”&lt;&#x2F;a&gt;
and something that could conceivably by called an OS kernel.
The next step is to launch a user-mode program that makes a &lt;em&gt;system call&lt;&#x2F;em&gt; that prints
“Hello, World!” inside the kernel.&lt;&#x2F;p&gt;
&lt;p&gt;The current problem I’m facing is getting a second program (in addition to the kernel)
loaded into memory during boot. I don’t want to load it from disk, as that would
require disk drivers and a filesystem. I don’t want to load an ELF image from memory,
as that would require an ELF loader.
The bootloader I’m using (&lt;a href=&quot;https:&#x2F;&#x2F;wiki.osdev.org&#x2F;BOOTBOOT&quot;&gt;bootboot&lt;&#x2F;a&gt;)
sets up an initial address space for the kernel based on an ELF file, so I’ll add a user-level
program directly to the kernel address space, and have the kernel start executing it
in user-mode once the system is brought up.&lt;&#x2F;p&gt;
&lt;p&gt;I can take the kernel code, and the code for a second, user-level program, and combine them into
a single ELF file using a linker script to position the two programs at sensible locations in
memory. When bootboot attempts to make a bootable disk image out of this ELF, it gets confused
and appears to treat the user-level program’s entry point as the kernel’s entry point.
This is understandable, since the single ELF actually contains two complete programs,
and I’ve done nothing to specify which one should be started when the ELF is executed.&lt;&#x2F;p&gt;
&lt;p&gt;I need to go learn more about ELF and static linking.&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>In Homogeneous Coordinates Addition is Multiplication</title>
          <pubDate>Wed, 19 Aug 2020 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/daily/in-homogeneous-coordinates-addition-is-multiplication/</link>
          <guid>https://www.gridbugs.org/daily/in-homogeneous-coordinates-addition-is-multiplication/</guid>
          <description xml:base="https://www.gridbugs.org/daily/in-homogeneous-coordinates-addition-is-multiplication/">&lt;p&gt;Yesterday I wrote about &lt;a href=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;daily&#x2F;homogeneous-coordinates&#x2F;&quot;&gt;Homogeneous Coordinates&lt;&#x2F;a&gt;.
I used 3D perspective projection as a motivating example, but I should have used &lt;em&gt;translation&lt;&#x2F;em&gt;.
To translate a point represented by a Cartesian coordinate, one simply adds the intended
delta in each dimension to the corresponding component of the coordinate. This can be done using vector addition.&lt;&#x2F;p&gt;
&lt;p&gt;In &lt;a href=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;daily&#x2F;homogeneous-coordinates&#x2F;&quot;&gt;yesterday’s post&lt;&#x2F;a&gt; I explained why it’s valuable to express as much as
possible using matrix
multiplication. Homogeneous coordinates let you express vector addition as matrix multiplication.
Convince yourself that this can’t be done using Cartesian coordinates.
Consider a 1-dimensional coordinate (just a number) &lt;code&gt;x&lt;&#x2F;code&gt;. Can you name a constant &lt;code&gt;a&lt;&#x2F;code&gt; such that for all &lt;code&gt;x&lt;&#x2F;code&gt;,
&lt;code&gt;x * a == x + 1&lt;&#x2F;code&gt;? Similarly, given a vector ‘v’ representing a point in a particular dimensional space, you can’t
construct a matrix ‘M’ such that &lt;code&gt;M * v == C + v&lt;&#x2F;code&gt; for some constant vector &lt;code&gt;C&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Using homogeneous coordinates it’s possible to add vectors through matrix multiplication.
The matrix that adds the vector &lt;code&gt;[a, b, c]&lt;&#x2F;code&gt; to all vectors &lt;code&gt;[x, y, z]&lt;&#x2F;code&gt; is:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;|-       -| |- -|   |-     -|
&lt;&#x2F;span&gt;&lt;span&gt;| 1 0 0 a | | x |   | x + a |
&lt;&#x2F;span&gt;&lt;span&gt;| 0 1 0 b | | y | = | y + b |
&lt;&#x2F;span&gt;&lt;span&gt;| 0 0 1 c | | z |   | z + c |
&lt;&#x2F;span&gt;&lt;span&gt;| 0 0 0 1 | | 1 |   |   1   |
&lt;&#x2F;span&gt;&lt;span&gt;|-       -| |- -|   |-     -|
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;We added a 4th element to the vector. Above its value is &lt;code&gt;1&lt;&#x2F;code&gt;, but in general it’s treated as a scale factor &lt;code&gt;w&lt;&#x2F;code&gt;.
We might end up wanting to translate a vector with a scale factor of (say) 2. Observe that in the matrix
multiplication above, the scale factor would be multiplied by &lt;code&gt;a&lt;&#x2F;code&gt;, &lt;code&gt;b&lt;&#x2F;code&gt;, and &lt;code&gt;c&lt;&#x2F;code&gt;, and the scale factor of
the result will be the scale factor of the vector we’re translating. When converting back to Cartesian coordinates,
divide everything by the scale factor, then remove it.&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Homogeneous Coordinates</title>
          <pubDate>Tue, 18 Aug 2020 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/daily/homogeneous-coordinates/</link>
          <guid>https://www.gridbugs.org/daily/homogeneous-coordinates/</guid>
          <description xml:base="https://www.gridbugs.org/daily/homogeneous-coordinates/">&lt;p&gt;Matrix multiplication is awesome. If you want to do a sequence of operations on
a bunch of vertices, by expressing each operation as a matrix multiplication,
you can use the fact that matrix multiplication is associative to “compile”
all the matrices into a single matrix, by multiplying them together.
Then, just multiply the result by each vertex.
Your code will be more efficient because the per-vertex operations are replaced
with a single matrix multiplication.
You may find your code becomes easier to read too, as a sequence of custom
vector operations are replaced by a single matrix multiplication per vector.&lt;&#x2F;p&gt;
&lt;p&gt;To get the full benefit of this, everything you do with vectors should be replaced
by a single sequential chain of matrix multiplications.&lt;&#x2F;p&gt;
&lt;p&gt;Homogeneous coordinates let you express more operations as matrix multiplications.
In a homogeneous coordinate system, each vector gets an additional element. E.g.
a 3D point is now made up of 4 numbers, rather than 3, as would be the case in
a Cartesian coordinate system. When converting from Cartesian to homogeneous
coordinates, choose 1 as the additional element. When converting back, divide
each element by the value of the extra element, then remove the extra element.&lt;&#x2F;p&gt;
&lt;p&gt;For a motivating example, consider perspective projection. That’s the process of
taking a point in 3D space relative to a camera, and determining the coordinate
of the pixel in the image that the camera sees. It’s commonly used in video games
to convert 3D points in the world of the game into pixel coordinates that get
drawn to the screen. To convert camera relative point &lt;code&gt;[x, y, z]&lt;&#x2F;code&gt; into a pixel
coordinate &lt;code&gt;[u, v]&lt;&#x2F;code&gt;, where x+ is right, y+ is upwards, z+ is away from the camera,
and u+ is to the right on you screen, and v+ is upwards on your screen, and the
point &lt;code&gt;[0, 0]&lt;&#x2F;code&gt; is the centre of your screen:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;u = x * cotan(fov_x &#x2F; 2) &#x2F; z
&lt;&#x2F;span&gt;&lt;span&gt;v = y * cotan(fov_y &#x2F; 2) &#x2F; z
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Here, &lt;code&gt;fov_x&lt;&#x2F;code&gt; and &lt;code&gt;fov_y&lt;&#x2F;code&gt; are the horizontal and vertical field of view of the
imaginary camera taking the photo.
The intuition behind this is that things move towards the centre of your vision
as they move away from you due to perspective, so dividing by &lt;code&gt;z&lt;&#x2F;code&gt; moves the screen
coordinates towarsd the centre of the screen (remember &lt;code&gt;[0, 0]&lt;&#x2F;code&gt; is the centre of the screen).&lt;&#x2F;p&gt;
&lt;p&gt;The above logic works, but computing perspective projection is often part of a sequence
of operations a game (say) will do on each vertex (each point in the world) each frame.
To get the full benefit of chaining together matrix multiplications, it would be useful
if the above logic could be expressed as a matrix multiplication too.
A first attempt might be:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;|-                                  -|  |-     -|   |-                        -|
&lt;&#x2F;span&gt;&lt;span&gt;| cotan(fov_x &#x2F; 2)          0        |  | x &#x2F; z | = | x * cotan(fov_x &#x2F; 2) &#x2F; z |
&lt;&#x2F;span&gt;&lt;span&gt;|         0         cotan(fov_y &#x2F; 2) |  | y &#x2F; z |   | y * cotan(fov_y &#x2F; 2) &#x2F; z |
&lt;&#x2F;span&gt;&lt;span&gt;|-                                  -|  |-     -|   |-                        -|
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This would work, but requires pre-processing each point, turning &lt;code&gt;[x, y, z]&lt;&#x2F;code&gt; into &lt;code&gt;[x&#x2F;z, y&#x2F;z]&lt;&#x2F;code&gt;.
This pre-processing is not a matrix multiplication, so messes up our pipeline of matrix
multiplications.&lt;&#x2F;p&gt;
&lt;p&gt;Enter homogeneous coordinates:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;|-                                      -|  |- -|   |-                    -|
&lt;&#x2F;span&gt;&lt;span&gt;| cotan(fov_x &#x2F; 2)          0         0  |  | x |   | x * cotan(fov_x &#x2F; 2) |
&lt;&#x2F;span&gt;&lt;span&gt;|         0         cotan(fov_y &#x2F; 2)  0  |  | y | = | y * cotan(fov_y &#x2F; 2) |
&lt;&#x2F;span&gt;&lt;span&gt;|         0                 0         1  |  | z |   |          z           |
&lt;&#x2F;span&gt;&lt;span&gt;|-                                      -|  |- -|   |-                    -|
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;When using this operation in the context of computer graphics, it’s likely
part of a sequence of matrix multiplications that convert 3D points in the world
into 2D points on a screen. The result of the above multiplication is a 3D vector.
To compute the pixel coordinate &lt;code&gt;[u, v]&lt;&#x2F;code&gt; from the output, divide each component
by &lt;code&gt;z&lt;&#x2F;code&gt; (the extra component), and then remove the &lt;code&gt;z&lt;&#x2F;code&gt; component altogether.&lt;&#x2F;p&gt;
&lt;p&gt;A caveat to the above is that the &lt;code&gt;[x, y, z]&lt;&#x2F;code&gt; vector is not itself an homogeneous
coordinate, but we treat the result of the multiplication as an homogeneous coordinate.
In practice, it’s likely that the vector on the right-hand-side of the multiplication
will have an additional component, and the matrix on the left-hand-side will have an
additional row and column.&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Printing over serial on x86</title>
          <pubDate>Mon, 17 Aug 2020 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/daily/printing-over-serial-on-x86/</link>
          <guid>https://www.gridbugs.org/daily/printing-over-serial-on-x86/</guid>
          <description xml:base="https://www.gridbugs.org/daily/printing-over-serial-on-x86/">&lt;p&gt;Tonight I set myself the task of printing some text over a serial port in a tiny
kernel mode program that boots with the &lt;a href=&quot;https:&#x2F;&#x2F;wiki.osdev.org&#x2F;BOOTBOOT&quot;&gt;BOOTBOOT&lt;&#x2F;a&gt;
bootloader. I didn’t start from scratch, but from some
&lt;a href=&quot;https:&#x2F;&#x2F;gitlab.com&#x2F;bztsrc&#x2F;bootboot&#x2F;-&#x2F;tree&#x2F;master&#x2F;mykernel&quot;&gt;example code&lt;&#x2F;a&gt;
in the BOOTBOOT reference implementation.&lt;&#x2F;p&gt;
&lt;p&gt;The only reason it wasn’t trivial is that I’m targeting a UEFI machine,
and between the &lt;a href=&quot;https:&#x2F;&#x2F;gitlab.com&#x2F;bztsrc&#x2F;bootboot&#x2F;raw&#x2F;master&#x2F;bootboot_spec_1st_ed.pdf&quot;&gt;BOOTBOOT spec&lt;&#x2F;a&gt;
and the &lt;a href=&quot;https:&#x2F;&#x2F;wiki.osdev.org&#x2F;Serial_Ports&quot;&gt;osdev wiki page about serial ports&lt;&#x2F;a&gt;
or &lt;a href=&quot;https:&#x2F;&#x2F;wiki.osdev.org&#x2F;UEFI&quot;&gt;UEFI&lt;&#x2F;a&gt;, it’s not clear whether the “old fashioned” way
of interacting with serial ports is supported on UEFI. All the information I could find about
how to print via serial console on UEFI systems implied that I should use a library which
abstracts access to hardware via UEFI.&lt;&#x2F;p&gt;
&lt;p&gt;Instead I elected to try doing it the “old fashioned” way, based on the
&lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;seL4&#x2F;seL4&#x2F;blob&#x2F;master&#x2F;src&#x2F;plat&#x2F;pc99&#x2F;machine&#x2F;io.c#L30&quot;&gt;debug printing code from the seL4 microkernel&lt;&#x2F;a&gt;.
Here’s how it looks.&lt;&#x2F;p&gt;
&lt;p&gt;I added a small assembly file defining a &lt;code&gt;com1_putc&lt;&#x2F;code&gt; function that sends its argument to I&#x2F;O port &lt;code&gt;0x3F8&lt;&#x2F;code&gt;,
which conventionally addresses the &lt;code&gt;COM1&lt;&#x2F;code&gt; serial port.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;nasm&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-nasm &quot;&gt;&lt;code class=&quot;language-nasm&quot; data-lang=&quot;nasm&quot;&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;; ioports.asm
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;global &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;com1_putc
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;%define &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;COM1 &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;0x3F8
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;section &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;.text
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;bits 64
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;com1_putc:
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;mov &lt;&#x2F;span&gt;&lt;span&gt;rax, rdi
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;mov &lt;&#x2F;span&gt;&lt;span&gt;dx, &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;COM1
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;out &lt;&#x2F;span&gt;&lt;span&gt;dx, al
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;ret
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Assemble the new file:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;make&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-make &quot;&gt;&lt;code class=&quot;language-make&quot; data-lang=&quot;make&quot;&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;# Makefile
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;mykernel.x86_64.elf&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;kernel.c
&lt;&#x2F;span&gt;&lt;span&gt;	nasm -f elf64 ioports.asm -o ioports.o
&lt;&#x2F;span&gt;&lt;span&gt;	... (add ioports.o to the linker arguments)
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;And call the new function from &lt;code&gt;_start&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;c&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-c &quot;&gt;&lt;code class=&quot;language-c&quot; data-lang=&quot;c&quot;&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; kernel.c
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;void &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;com1_putc&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;char &lt;&#x2F;span&gt;&lt;span&gt;c);
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;void &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;com1_puts&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;char* &lt;&#x2F;span&gt;&lt;span&gt;s);
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;void &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;_start&lt;&#x2F;span&gt;&lt;span&gt;()
&lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;    com1_puts(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;Hello, COM1&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;\n&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;);
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;void &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;com1_puts&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;char* &lt;&#x2F;span&gt;&lt;span&gt;s) {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;do &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;        com1_putc(&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;*&lt;&#x2F;span&gt;&lt;span&gt;(s&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;++&lt;&#x2F;span&gt;&lt;span&gt;));
&lt;&#x2F;span&gt;&lt;span&gt;    } &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;while &lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;*&lt;&#x2F;span&gt;&lt;span&gt;s);
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Despite its simplicity, I’m not convinced that this code is correct in all cases.
I rushed this because I just wanted to get something working, and now that it works
for me, I’ll slow down and understand this code as deeply as I can before moving on.&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Trekking is good because it&#x27;s a problem you can solve</title>
          <pubDate>Sun, 16 Aug 2020 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/daily/trekking-is-good-because-it-s-a-problem-you-can-solve/</link>
          <guid>https://www.gridbugs.org/daily/trekking-is-good-because-it-s-a-problem-you-can-solve/</guid>
          <description xml:base="https://www.gridbugs.org/daily/trekking-is-good-because-it-s-a-problem-you-can-solve/">&lt;p&gt;Early this year I spent a week trekking in Nepal.
This is a brief reflection on one aspect.&lt;&#x2F;p&gt;
&lt;p&gt;I’m an engineer; I solve problems for fun and profit.
Working at a problem is most satisfying when you know for sure that
all the effort you’re putting in is valuable. Inevitably, this won’t
be true all of the time. Some work is exploratory, where the precise
nature of the solution won’t become apparent until some experimentation
takes place. Sometimes it’s not clear to everyone in a team that the agreed-upon solution
will actually solve the problem at hand. You may embark on solving a problem
without knowing for sure whether it can even be solved.&lt;&#x2F;p&gt;
&lt;p&gt;This uncertainty is a cause of stress, especially in a workplace whose culture which denies its existence.&lt;&#x2F;p&gt;
&lt;p&gt;There is no uncertainty in trekking (assuming you have a guide who knows where they’re going
and you’re physically able). If accommodation is organized, and you’re prepared in terms of
water, medication, etc, the &lt;em&gt;only&lt;&#x2F;em&gt; problem you need to solve is the fact that you aren’t at
your destination. The &lt;em&gt;only&lt;&#x2F;em&gt; way to solve this problem is by walking towards your destination,
and you know &lt;em&gt;for sure&lt;&#x2F;em&gt; that every step towards your destination is useful work.&lt;&#x2F;p&gt;
&lt;p&gt;Engineering would not be interesting if all solutions were so simple,
but it is refreshing, once in a while, to be presented with a problem
where one can make fearless incremental progress.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;daily&#x2F;trekking-is-good-because-it-s-a-problem-you-can-solve&#x2F;nepal.jpg&quot; alt=&quot;nepal.jpg&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Part 13 - Equipment</title>
          <pubDate>Sat, 15 Aug 2020 19:40:00 +1000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/roguelike-tutorial-2020-part-13/</link>
          <guid>https://www.gridbugs.org/roguelike-tutorial-2020-part-13/</guid>
          <description xml:base="https://www.gridbugs.org/roguelike-tutorial-2020-part-13/">&lt;p&gt;This is the final part of the tutorial, in which we’ll add equipment.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;roguelike-tutorial-2020-part-13&#x2F;screenshot.png&quot; alt=&quot;screenshot.png&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;This part is loosely based on &lt;a href=&quot;http:&#x2F;&#x2F;rogueliketutorials.com&#x2F;tutorials&#x2F;tcod&#x2F;part-13&#x2F;&quot;&gt;this part&lt;&#x2F;a&gt; of the
python tcod tutorial.&lt;&#x2F;p&gt;
&lt;p&gt;Reference implementation branch for starting point: &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;gridbugs&#x2F;chargrid-roguelike-tutorial-2020&#x2F;tree&#x2F;part-12-end&quot;&gt;part-12-end&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;p&gt;In this post:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;roguelike-tutorial-2020-part-13&#x2F;#equipment-entities&quot;&gt;Equipment Entities&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;roguelike-tutorial-2020-part-13&#x2F;#equipable-equipment&quot;&gt;Equipable Equipment&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;roguelike-tutorial-2020-part-13&#x2F;#modifying-stats-with-equipment&quot;&gt;Modifying Stats with Equipment&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;roguelike-tutorial-2020-part-13&#x2F;#balance-item-distribution&quot;&gt;Balance Item Distribution&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;roguelike-tutorial-2020-part-13&#x2F;#log-message-for-equipment&quot;&gt;Log Message for Equipment&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;equipment-entities&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#equipment-entities&quot; aria-label=&quot;Anchor link for: equipment-entities&quot;&gt;Equipment Entities&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;Add additional item types to represent equipment.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; world.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;pub enum &lt;&#x2F;span&gt;&lt;span&gt;ItemType {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;    Sword,
&lt;&#x2F;span&gt;&lt;span&gt;    Staff,
&lt;&#x2F;span&gt;&lt;span&gt;    Armour,
&lt;&#x2F;span&gt;&lt;span&gt;    Robe,
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;impl &lt;&#x2F;span&gt;&lt;span&gt;ItemType {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;pub fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;name&lt;&#x2F;span&gt;&lt;span&gt;(self) -&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&amp;#39;static str &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;match &lt;&#x2F;span&gt;&lt;span&gt;self {
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;Self&lt;&#x2F;span&gt;&lt;span&gt;::Sword &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;sword&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;Self&lt;&#x2F;span&gt;&lt;span&gt;::Staff &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;staff&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;Self&lt;&#x2F;span&gt;&lt;span&gt;::Armour &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;armour&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;Self&lt;&#x2F;span&gt;&lt;span&gt;::Robe &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;robe&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;        }
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;impl &lt;&#x2F;span&gt;&lt;span&gt;World {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;pub fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;maybe_use_item&lt;&#x2F;span&gt;&lt;span&gt;(
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut &lt;&#x2F;span&gt;&lt;span&gt;self,
&lt;&#x2F;span&gt;&lt;span&gt;        character: Entity,
&lt;&#x2F;span&gt;&lt;span&gt;        inventory_index: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;usize&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;        message_log: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Vec&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;LogMessage&amp;gt;,
&lt;&#x2F;span&gt;&lt;span&gt;    ) -&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Result&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;ItemUsage, ()&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; usage &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= match&lt;&#x2F;span&gt;&lt;span&gt; item_type {
&lt;&#x2F;span&gt;&lt;span&gt;            ItemType::HealthPotion &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;                &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;            }
&lt;&#x2F;span&gt;&lt;span&gt;            ItemType::FireballScroll &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;| &lt;&#x2F;span&gt;&lt;span&gt;ItemType::ConfusionScroll &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span&gt;ItemUsage::Aim,
&lt;&#x2F;span&gt;&lt;span&gt;            ItemType::Sword &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;| &lt;&#x2F;span&gt;&lt;span&gt;ItemType::Staff &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;| &lt;&#x2F;span&gt;&lt;span&gt;ItemType::Armour &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;| &lt;&#x2F;span&gt;&lt;span&gt;ItemType::Robe &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span&gt;todo!(),
&lt;&#x2F;span&gt;&lt;span&gt;        };
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;pub fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;maybe_use_item_aim&lt;&#x2F;span&gt;&lt;span&gt;(
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut &lt;&#x2F;span&gt;&lt;span&gt;self,
&lt;&#x2F;span&gt;&lt;span&gt;        character: Entity,
&lt;&#x2F;span&gt;&lt;span&gt;        inventory_index: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;usize&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;        target: Coord,
&lt;&#x2F;span&gt;&lt;span&gt;        message_log: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Vec&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;LogMessage&amp;gt;,
&lt;&#x2F;span&gt;&lt;span&gt;    ) -&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Result&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;(), ()&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;match&lt;&#x2F;span&gt;&lt;span&gt; item_type {
&lt;&#x2F;span&gt;&lt;span&gt;            ItemType::HealthPotion
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;| &lt;&#x2F;span&gt;&lt;span&gt;ItemType::Sword
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;| &lt;&#x2F;span&gt;&lt;span&gt;ItemType::Staff
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;| &lt;&#x2F;span&gt;&lt;span&gt;ItemType::Armour
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;| &lt;&#x2F;span&gt;&lt;span&gt;ItemType::Robe &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span&gt;panic!(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;invalid item for aim&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;),
&lt;&#x2F;span&gt;&lt;span&gt;            ItemType::FireballScroll &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;                &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;            }
&lt;&#x2F;span&gt;&lt;span&gt;            ItemType::ConfusionScroll &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;                &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;            }
&lt;&#x2F;span&gt;&lt;span&gt;        }
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Add equipment to the item probability distribution. Set the probability of each to 1000
for now so it’s highly likely that all items will be equipment. This will help us test.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; terrain.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;make_item_probability_distribution&lt;&#x2F;span&gt;&lt;span&gt;(level: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;u32&lt;&#x2F;span&gt;&lt;span&gt;) -&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Vec&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;(ItemType, &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;u32&lt;&#x2F;span&gt;&lt;span&gt;)&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;use &lt;&#x2F;span&gt;&lt;span&gt;ItemType::&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;*&lt;&#x2F;span&gt;&lt;span&gt;;
&lt;&#x2F;span&gt;&lt;span&gt;    vec![
&lt;&#x2F;span&gt;&lt;span&gt;        (HealthPotion, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;20&lt;&#x2F;span&gt;&lt;span&gt;),
&lt;&#x2F;span&gt;&lt;span&gt;        (
&lt;&#x2F;span&gt;&lt;span&gt;            FireballScroll,
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;match&lt;&#x2F;span&gt;&lt;span&gt; level {
&lt;&#x2F;span&gt;&lt;span&gt;                &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;..=&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;1 &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;                &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;2&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;..=&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;4 &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;5&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;                &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;_ =&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;10&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;            },
&lt;&#x2F;span&gt;&lt;span&gt;        ),
&lt;&#x2F;span&gt;&lt;span&gt;        (
&lt;&#x2F;span&gt;&lt;span&gt;            ConfusionScroll,
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;match&lt;&#x2F;span&gt;&lt;span&gt; level {
&lt;&#x2F;span&gt;&lt;span&gt;                &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;..=&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;1 &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;                &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;2&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;..=&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;4 &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;3&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;                &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;_ =&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;5&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;            },
&lt;&#x2F;span&gt;&lt;span&gt;        ),
&lt;&#x2F;span&gt;&lt;span&gt;        (Sword, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;1000&lt;&#x2F;span&gt;&lt;span&gt;),
&lt;&#x2F;span&gt;&lt;span&gt;        (Staff, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;1000&lt;&#x2F;span&gt;&lt;span&gt;),
&lt;&#x2F;span&gt;&lt;span&gt;        (Armour, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;1000&lt;&#x2F;span&gt;&lt;span&gt;),
&lt;&#x2F;span&gt;&lt;span&gt;        (Robe, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;1000&lt;&#x2F;span&gt;&lt;span&gt;),
&lt;&#x2F;span&gt;&lt;span&gt;    ]
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Add some code for rendering the new item types.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; app.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;pub mod &lt;&#x2F;span&gt;&lt;span&gt;colours {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;pub const &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;SWORD&lt;&#x2F;span&gt;&lt;span&gt;: Rgb24 &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span&gt;Rgb24::new(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;187&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;187&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;187&lt;&#x2F;span&gt;&lt;span&gt;);
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;pub const &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;STAFF&lt;&#x2F;span&gt;&lt;span&gt;: Rgb24 &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span&gt;Rgb24::new(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;187&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;127&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;255&lt;&#x2F;span&gt;&lt;span&gt;);
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;pub const &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;ARMOUR&lt;&#x2F;span&gt;&lt;span&gt;: Rgb24 &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span&gt;Rgb24::new(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;127&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;127&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;127&lt;&#x2F;span&gt;&lt;span&gt;);
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;pub const &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;ROBE&lt;&#x2F;span&gt;&lt;span&gt;: Rgb24 &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span&gt;Rgb24::new(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;127&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;127&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;187&lt;&#x2F;span&gt;&lt;span&gt;);
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;pub fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;item_colour&lt;&#x2F;span&gt;&lt;span&gt;(item_type: ItemType) -&amp;gt; Rgb24 {
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;match&lt;&#x2F;span&gt;&lt;span&gt; item_type {
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;            ItemType::Sword &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;SWORD&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;            ItemType::Staff &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;STAFF&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;            ItemType::Armour &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;ARMOUR&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;            ItemType::Robe &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;ROBE&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;        }
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;currently_visible_view_cell_of_tile&lt;&#x2F;span&gt;&lt;span&gt;(tile: Tile) -&amp;gt; ViewCell {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;match&lt;&#x2F;span&gt;&lt;span&gt; tile {
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;        Tile::Item(ItemType::Sword) &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span&gt;ViewCell::new()
&lt;&#x2F;span&gt;&lt;span&gt;            .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;with_bold&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;true&lt;&#x2F;span&gt;&lt;span&gt;)
&lt;&#x2F;span&gt;&lt;span&gt;            .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;with_character&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;#39;&#x2F;&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;)
&lt;&#x2F;span&gt;&lt;span&gt;            .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;with_foreground&lt;&#x2F;span&gt;&lt;span&gt;(colours::&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;SWORD&lt;&#x2F;span&gt;&lt;span&gt;),
&lt;&#x2F;span&gt;&lt;span&gt;        Tile::Item(ItemType::Staff) &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span&gt;ViewCell::new()
&lt;&#x2F;span&gt;&lt;span&gt;            .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;with_bold&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;true&lt;&#x2F;span&gt;&lt;span&gt;)
&lt;&#x2F;span&gt;&lt;span&gt;            .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;with_character&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;\\&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;)
&lt;&#x2F;span&gt;&lt;span&gt;            .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;with_foreground&lt;&#x2F;span&gt;&lt;span&gt;(colours::&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;STAFF&lt;&#x2F;span&gt;&lt;span&gt;),
&lt;&#x2F;span&gt;&lt;span&gt;        Tile::Item(ItemType::Armour) &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span&gt;ViewCell::new()
&lt;&#x2F;span&gt;&lt;span&gt;            .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;with_bold&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;true&lt;&#x2F;span&gt;&lt;span&gt;)
&lt;&#x2F;span&gt;&lt;span&gt;            .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;with_character&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;#39;]&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;)
&lt;&#x2F;span&gt;&lt;span&gt;            .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;with_foreground&lt;&#x2F;span&gt;&lt;span&gt;(colours::&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;ARMOUR&lt;&#x2F;span&gt;&lt;span&gt;),
&lt;&#x2F;span&gt;&lt;span&gt;        Tile::Item(ItemType::Robe) &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span&gt;ViewCell::new()
&lt;&#x2F;span&gt;&lt;span&gt;            .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;with_bold&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;true&lt;&#x2F;span&gt;&lt;span&gt;)
&lt;&#x2F;span&gt;&lt;span&gt;            .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;with_character&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;#39;}&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;)
&lt;&#x2F;span&gt;&lt;span&gt;            .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;with_foreground&lt;&#x2F;span&gt;&lt;span&gt;(colours::&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;ROBE&lt;&#x2F;span&gt;&lt;span&gt;),
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The game will now populate levels with equipment.
Since equipment are just regular items, they can already be picked up, dropped, and viewed in
the inventory. Attempting to use a piece of equipment will panic at the moment (the &lt;code&gt;todo!()&lt;&#x2F;code&gt; macro).&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;roguelike-tutorial-2020-part-13&#x2F;items.png&quot; alt=&quot;items.png&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Reference implementation branch: &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;gridbugs&#x2F;chargrid-roguelike-tutorial-2020&#x2F;tree&#x2F;part-13.0&quot;&gt;part-13.0&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;h2 id=&quot;equipable-equipment&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#equipable-equipment&quot; aria-label=&quot;Anchor link for: equipable-equipment&quot;&gt;Equipable Equipment&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;We’ll allow equipment to be equipped in two slots. Armour and robes go in the “worn” slot,
and swords and staffs go in the “held” slot.
We’ll keep track of whit is equipped by adding two components - &lt;code&gt;equipment_worn_inventory_index&lt;&#x2F;code&gt; and
&lt;code&gt;equipment_held_inventory_index&lt;&#x2F;code&gt; - which will store the index in the player’s inventory containing the
items equipped in the respective slots.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; world.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;entity_table::declare_entity_module&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;! &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;    components {
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;        equipment_worn_inventory_index: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;usize&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;        equipment_held_inventory_index: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;usize&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Replace the &lt;code&gt;todo!()&lt;&#x2F;code&gt; from the previous section to allow equipment items to be used from the inventory menu,
which will cause the item to be equipped.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; world.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;impl &lt;&#x2F;span&gt;&lt;span&gt;World {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;pub fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;maybe_use_item&lt;&#x2F;span&gt;&lt;span&gt;(
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut &lt;&#x2F;span&gt;&lt;span&gt;self,
&lt;&#x2F;span&gt;&lt;span&gt;        character: Entity,
&lt;&#x2F;span&gt;&lt;span&gt;        inventory_index: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;usize&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;        message_log: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Vec&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;LogMessage&amp;gt;,
&lt;&#x2F;span&gt;&lt;span&gt;    ) -&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Result&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;ItemUsage, ()&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; usage &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= match&lt;&#x2F;span&gt;&lt;span&gt; item_type {
&lt;&#x2F;span&gt;&lt;span&gt;            ItemType::HealthPotion &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;                &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;            }
&lt;&#x2F;span&gt;&lt;span&gt;            ItemType::FireballScroll &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;| &lt;&#x2F;span&gt;&lt;span&gt;ItemType::ConfusionScroll &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span&gt;ItemUsage::Aim,
&lt;&#x2F;span&gt;&lt;span&gt;            ItemType::Sword &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;| &lt;&#x2F;span&gt;&lt;span&gt;ItemType::Staff &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;                self.components
&lt;&#x2F;span&gt;&lt;span&gt;                    .equipment_held_inventory_index
&lt;&#x2F;span&gt;&lt;span&gt;                    .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;insert&lt;&#x2F;span&gt;&lt;span&gt;(character, inventory_index);
&lt;&#x2F;span&gt;&lt;span&gt;                ItemUsage::Immediate
&lt;&#x2F;span&gt;&lt;span&gt;            }
&lt;&#x2F;span&gt;&lt;span&gt;            ItemType::Armour &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;| &lt;&#x2F;span&gt;&lt;span&gt;ItemType::Robe &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;                self.components
&lt;&#x2F;span&gt;&lt;span&gt;                    .equipment_worn_inventory_index
&lt;&#x2F;span&gt;&lt;span&gt;                    .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;insert&lt;&#x2F;span&gt;&lt;span&gt;(character, inventory_index);
&lt;&#x2F;span&gt;&lt;span&gt;                ItemUsage::Immediate
&lt;&#x2F;span&gt;&lt;span&gt;            }
&lt;&#x2F;span&gt;&lt;span&gt;        };
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Update the logic for dropping items so that equipped items are unequipped if they are dropped.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; world.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;impl &lt;&#x2F;span&gt;&lt;span&gt;World {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;pub fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;maybe_drop_item&lt;&#x2F;span&gt;&lt;span&gt;(
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut &lt;&#x2F;span&gt;&lt;span&gt;self,
&lt;&#x2F;span&gt;&lt;span&gt;        character: Entity,
&lt;&#x2F;span&gt;&lt;span&gt;        inventory_index: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;usize&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;        message_log: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Vec&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;LogMessage&amp;gt;,
&lt;&#x2F;span&gt;&lt;span&gt;    ) -&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Result&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;(), ()&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;if &lt;&#x2F;span&gt;&lt;span&gt;self
&lt;&#x2F;span&gt;&lt;span&gt;            .components
&lt;&#x2F;span&gt;&lt;span&gt;            .equipment_held_inventory_index
&lt;&#x2F;span&gt;&lt;span&gt;            .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;get&lt;&#x2F;span&gt;&lt;span&gt;(character)
&lt;&#x2F;span&gt;&lt;span&gt;            .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;cloned&lt;&#x2F;span&gt;&lt;span&gt;()
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;== &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Some&lt;&#x2F;span&gt;&lt;span&gt;(inventory_index)
&lt;&#x2F;span&gt;&lt;span&gt;        {
&lt;&#x2F;span&gt;&lt;span&gt;            self.components
&lt;&#x2F;span&gt;&lt;span&gt;                .equipment_held_inventory_index
&lt;&#x2F;span&gt;&lt;span&gt;                .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;remove&lt;&#x2F;span&gt;&lt;span&gt;(character);
&lt;&#x2F;span&gt;&lt;span&gt;        }
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;if &lt;&#x2F;span&gt;&lt;span&gt;self
&lt;&#x2F;span&gt;&lt;span&gt;            .components
&lt;&#x2F;span&gt;&lt;span&gt;            .equipment_worn_inventory_index
&lt;&#x2F;span&gt;&lt;span&gt;            .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;get&lt;&#x2F;span&gt;&lt;span&gt;(character)
&lt;&#x2F;span&gt;&lt;span&gt;            .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;cloned&lt;&#x2F;span&gt;&lt;span&gt;()
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;== &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Some&lt;&#x2F;span&gt;&lt;span&gt;(inventory_index)
&lt;&#x2F;span&gt;&lt;span&gt;        {
&lt;&#x2F;span&gt;&lt;span&gt;            self.components
&lt;&#x2F;span&gt;&lt;span&gt;                .equipment_worn_inventory_index
&lt;&#x2F;span&gt;&lt;span&gt;                .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;remove&lt;&#x2F;span&gt;&lt;span&gt;(character);
&lt;&#x2F;span&gt;&lt;span&gt;        }
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;To tell the player what they currently have equipped, we’ll update the inventory menu to display
the equipment slot occupied by equipped items. Add a type to &lt;code&gt;world.rs&lt;&#x2F;code&gt; representing the inventory
slot indices corresponding to equipped items (if any), and a method for returning the equipment
inventory slots of a given entity.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; world.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;pub struct &lt;&#x2F;span&gt;&lt;span&gt;EquippedInventoryIndices {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;pub &lt;&#x2F;span&gt;&lt;span&gt;worn: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Option&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;usize&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;,
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;pub &lt;&#x2F;span&gt;&lt;span&gt;held: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Option&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;usize&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;,
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;impl &lt;&#x2F;span&gt;&lt;span&gt;World {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;pub fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;equipped_inventory_indices&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;self, entity: Entity) -&amp;gt; EquippedInventoryIndices {
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; held &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span&gt;self
&lt;&#x2F;span&gt;&lt;span&gt;            .components
&lt;&#x2F;span&gt;&lt;span&gt;            .equipment_held_inventory_index
&lt;&#x2F;span&gt;&lt;span&gt;            .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;get&lt;&#x2F;span&gt;&lt;span&gt;(entity)
&lt;&#x2F;span&gt;&lt;span&gt;            .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;cloned&lt;&#x2F;span&gt;&lt;span&gt;();
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; worn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span&gt;self
&lt;&#x2F;span&gt;&lt;span&gt;            .components
&lt;&#x2F;span&gt;&lt;span&gt;            .equipment_worn_inventory_index
&lt;&#x2F;span&gt;&lt;span&gt;            .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;get&lt;&#x2F;span&gt;&lt;span&gt;(entity)
&lt;&#x2F;span&gt;&lt;span&gt;            .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;cloned&lt;&#x2F;span&gt;&lt;span&gt;();
&lt;&#x2F;span&gt;&lt;span&gt;        EquippedInventoryIndices { held, worn }
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Add a method to &lt;code&gt;GameState&lt;&#x2F;code&gt; for querying the player’s &lt;code&gt;EquippedInventoryIndices&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; game.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;use crate&lt;&#x2F;span&gt;&lt;span&gt;::world::{
&lt;&#x2F;span&gt;&lt;span&gt;    EquippedInventoryIndices, HitPoints, Inventory, ItemType, ItemUsage, Location, NpcType,
&lt;&#x2F;span&gt;&lt;span&gt;    Populate, ProjectileType, Tile, World,
&lt;&#x2F;span&gt;&lt;span&gt;};
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;impl &lt;&#x2F;span&gt;&lt;span&gt;GameState {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;pub fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;player_equipped_inventory_indices&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;self) -&amp;gt; EquippedInventoryIndices {
&lt;&#x2F;span&gt;&lt;span&gt;        self.world.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;equipped_inventory_indices&lt;&#x2F;span&gt;&lt;span&gt;(self.player_entity)
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Update the rendering logic for the inventory menu to show the equipment slots corresponding
to equipped items.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; app.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;impl&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;#39;a&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt; View&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&amp;#39;a&lt;&#x2F;span&gt;&lt;span&gt; AppData&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;for &lt;&#x2F;span&gt;&lt;span&gt;InventorySlotMenuView {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;view&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;F: Frame, C: ColModify&amp;gt;(
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut &lt;&#x2F;span&gt;&lt;span&gt;self,
&lt;&#x2F;span&gt;&lt;span&gt;        data: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&amp;#39;a&lt;&#x2F;span&gt;&lt;span&gt; AppData,
&lt;&#x2F;span&gt;&lt;span&gt;        context: ViewContext&amp;lt;C&amp;gt;,
&lt;&#x2F;span&gt;&lt;span&gt;        frame: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut&lt;&#x2F;span&gt;&lt;span&gt; F,
&lt;&#x2F;span&gt;&lt;span&gt;    ) {
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; equipped_indices &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; data.game_state.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;player_equipped_inventory_indices&lt;&#x2F;span&gt;&lt;span&gt;();
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;for &lt;&#x2F;span&gt;&lt;span&gt;((i, entry, maybe_selected), &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;slot) &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;in&lt;&#x2F;span&gt;&lt;span&gt; data
&lt;&#x2F;span&gt;&lt;span&gt;            .inventory_slot_menu
&lt;&#x2F;span&gt;&lt;span&gt;            .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;menu_instance&lt;&#x2F;span&gt;&lt;span&gt;()
&lt;&#x2F;span&gt;&lt;span&gt;            .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;enumerate&lt;&#x2F;span&gt;&lt;span&gt;()
&lt;&#x2F;span&gt;&lt;span&gt;            .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;zip&lt;&#x2F;span&gt;&lt;span&gt;(player_inventory_slots.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;into_iter&lt;&#x2F;span&gt;&lt;span&gt;())
&lt;&#x2F;span&gt;&lt;span&gt;        {
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; prefix &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span&gt;format!(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;{} {}&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;) &amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;, selected_prefix, entry.key);
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; equipment_suffix &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= if&lt;&#x2F;span&gt;&lt;span&gt; equipped_indices.held &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;== &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Some&lt;&#x2F;span&gt;&lt;span&gt;(i) {
&lt;&#x2F;span&gt;&lt;span&gt;                &lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot; (held)&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;            } &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;else if&lt;&#x2F;span&gt;&lt;span&gt; equipped_indices.worn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;== &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Some&lt;&#x2F;span&gt;&lt;span&gt;(i) {
&lt;&#x2F;span&gt;&lt;span&gt;                &lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot; (worn)&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;            } &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;else &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;                &lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;            };
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; text &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= &amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;[
&lt;&#x2F;span&gt;&lt;span&gt;                &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;                RichTextPart {
&lt;&#x2F;span&gt;&lt;span&gt;                    text: equipment_suffix,
&lt;&#x2F;span&gt;&lt;span&gt;                    style: name_style,
&lt;&#x2F;span&gt;&lt;span&gt;                },
&lt;&#x2F;span&gt;&lt;span&gt;            ];
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;        }
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Now you can equip items by selecting them from the inventory. The name of their occupied equipment slot
will appear next to items in the inventory menu.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;roguelike-tutorial-2020-part-13&#x2F;inventory.png&quot; alt=&quot;inventory.png&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Reference implementation branch: &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;gridbugs&#x2F;chargrid-roguelike-tutorial-2020&#x2F;tree&#x2F;part-13.1&quot;&gt;part-13.1&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;h2 id=&quot;modifying-stats-with-equipment&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#modifying-stats-with-equipment&quot; aria-label=&quot;Anchor link for: modifying-stats-with-equipment&quot;&gt;Modifying Stats with Equipment&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;Right now you can equip items, but equipping an item doesn’t actually change the game at all.
Let’s make each item increase combat stats by a flat amount.&lt;&#x2F;p&gt;
&lt;p&gt;Add some methods to &lt;code&gt;World&lt;&#x2F;code&gt; for computing modifiers to various combat stats based on equipment.
The effect of equipping different items will be hard-coded into these methods.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; world.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;impl &lt;&#x2F;span&gt;&lt;span&gt;World {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;inventory_item_type&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;self, entity: Entity, index: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;usize&lt;&#x2F;span&gt;&lt;span&gt;) -&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Option&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;ItemType&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;        self.components.inventory.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;get&lt;&#x2F;span&gt;&lt;span&gt;(entity).&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;and_then&lt;&#x2F;span&gt;&lt;span&gt;(|inventory| {
&lt;&#x2F;span&gt;&lt;span&gt;            inventory
&lt;&#x2F;span&gt;&lt;span&gt;                .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;get&lt;&#x2F;span&gt;&lt;span&gt;(index)
&lt;&#x2F;span&gt;&lt;span&gt;                .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;ok&lt;&#x2F;span&gt;&lt;span&gt;()
&lt;&#x2F;span&gt;&lt;span&gt;                .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;and_then&lt;&#x2F;span&gt;&lt;span&gt;(|held_entity| self.components.item.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;get&lt;&#x2F;span&gt;&lt;span&gt;(held_entity).&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;cloned&lt;&#x2F;span&gt;&lt;span&gt;())
&lt;&#x2F;span&gt;&lt;span&gt;        })
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;damage_modifier&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;self, entity: Entity) -&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;i32 &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;        self.components
&lt;&#x2F;span&gt;&lt;span&gt;            .equipment_held_inventory_index
&lt;&#x2F;span&gt;&lt;span&gt;            .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;get&lt;&#x2F;span&gt;&lt;span&gt;(entity)
&lt;&#x2F;span&gt;&lt;span&gt;            .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;and_then&lt;&#x2F;span&gt;&lt;span&gt;(|&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;held_index| {
&lt;&#x2F;span&gt;&lt;span&gt;                self.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;inventory_item_type&lt;&#x2F;span&gt;&lt;span&gt;(entity, held_index)
&lt;&#x2F;span&gt;&lt;span&gt;                    .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;map&lt;&#x2F;span&gt;&lt;span&gt;(|item_type| &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;match&lt;&#x2F;span&gt;&lt;span&gt; item_type {
&lt;&#x2F;span&gt;&lt;span&gt;                        ItemType::Sword &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;                        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;_ =&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;                    })
&lt;&#x2F;span&gt;&lt;span&gt;            })
&lt;&#x2F;span&gt;&lt;span&gt;            .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;unwrap_or&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt;)
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;defense_modifier&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;self, entity: Entity) -&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;i32 &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;        self.components
&lt;&#x2F;span&gt;&lt;span&gt;            .equipment_worn_inventory_index
&lt;&#x2F;span&gt;&lt;span&gt;            .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;get&lt;&#x2F;span&gt;&lt;span&gt;(entity)
&lt;&#x2F;span&gt;&lt;span&gt;            .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;and_then&lt;&#x2F;span&gt;&lt;span&gt;(|&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;held_index| {
&lt;&#x2F;span&gt;&lt;span&gt;                self.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;inventory_item_type&lt;&#x2F;span&gt;&lt;span&gt;(entity, held_index)
&lt;&#x2F;span&gt;&lt;span&gt;                    .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;map&lt;&#x2F;span&gt;&lt;span&gt;(|item_type| &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;match&lt;&#x2F;span&gt;&lt;span&gt; item_type {
&lt;&#x2F;span&gt;&lt;span&gt;                        ItemType::Armour &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;                        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;_ =&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;                    })
&lt;&#x2F;span&gt;&lt;span&gt;            })
&lt;&#x2F;span&gt;&lt;span&gt;            .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;unwrap_or&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt;)
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;magic_modifier&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;self, entity: Entity) -&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;i32 &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; held &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span&gt;self
&lt;&#x2F;span&gt;&lt;span&gt;            .components
&lt;&#x2F;span&gt;&lt;span&gt;            .equipment_held_inventory_index
&lt;&#x2F;span&gt;&lt;span&gt;            .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;get&lt;&#x2F;span&gt;&lt;span&gt;(entity)
&lt;&#x2F;span&gt;&lt;span&gt;            .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;and_then&lt;&#x2F;span&gt;&lt;span&gt;(|&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;held_index| {
&lt;&#x2F;span&gt;&lt;span&gt;                self.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;inventory_item_type&lt;&#x2F;span&gt;&lt;span&gt;(entity, held_index)
&lt;&#x2F;span&gt;&lt;span&gt;                    .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;map&lt;&#x2F;span&gt;&lt;span&gt;(|item_type| &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;match&lt;&#x2F;span&gt;&lt;span&gt; item_type {
&lt;&#x2F;span&gt;&lt;span&gt;                        ItemType::Staff &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;                        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;_ =&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;                    })
&lt;&#x2F;span&gt;&lt;span&gt;            })
&lt;&#x2F;span&gt;&lt;span&gt;            .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;unwrap_or&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt;);
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; worn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span&gt;self
&lt;&#x2F;span&gt;&lt;span&gt;            .components
&lt;&#x2F;span&gt;&lt;span&gt;            .equipment_worn_inventory_index
&lt;&#x2F;span&gt;&lt;span&gt;            .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;get&lt;&#x2F;span&gt;&lt;span&gt;(entity)
&lt;&#x2F;span&gt;&lt;span&gt;            .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;and_then&lt;&#x2F;span&gt;&lt;span&gt;(|&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;held_index| {
&lt;&#x2F;span&gt;&lt;span&gt;                self.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;inventory_item_type&lt;&#x2F;span&gt;&lt;span&gt;(entity, held_index)
&lt;&#x2F;span&gt;&lt;span&gt;                    .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;map&lt;&#x2F;span&gt;&lt;span&gt;(|item_type| &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;match&lt;&#x2F;span&gt;&lt;span&gt; item_type {
&lt;&#x2F;span&gt;&lt;span&gt;                        ItemType::Robe &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;                        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;_ =&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;                    })
&lt;&#x2F;span&gt;&lt;span&gt;            })
&lt;&#x2F;span&gt;&lt;span&gt;            .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;unwrap_or&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt;);
&lt;&#x2F;span&gt;&lt;span&gt;        held &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;+&lt;&#x2F;span&gt;&lt;span&gt; worn
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Update &lt;code&gt;World::character_bump_attack&lt;&#x2F;code&gt; to take modifiers into account.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; world.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;impl &lt;&#x2F;span&gt;&lt;span&gt;World {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;character_bump_attack&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;R: Rng&amp;gt;(
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut &lt;&#x2F;span&gt;&lt;span&gt;self,
&lt;&#x2F;span&gt;&lt;span&gt;        victim: Entity,
&lt;&#x2F;span&gt;&lt;span&gt;        attacker: Entity,
&lt;&#x2F;span&gt;&lt;span&gt;        rng: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut&lt;&#x2F;span&gt;&lt;span&gt; R,
&lt;&#x2F;span&gt;&lt;span&gt;    ) -&amp;gt; BumpAttackOutcome {
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;let &amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;attacker_base_damage &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span&gt;self.components.base_damage.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;get&lt;&#x2F;span&gt;&lt;span&gt;(attacker).&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;unwrap&lt;&#x2F;span&gt;&lt;span&gt;();
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;let &amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;attacker_strength &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span&gt;self.components.strength.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;get&lt;&#x2F;span&gt;&lt;span&gt;(attacker).&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;unwrap&lt;&#x2F;span&gt;&lt;span&gt;();
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; attacker_damage_modifier &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span&gt;self.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;damage_modifier&lt;&#x2F;span&gt;&lt;span&gt;(attacker);
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;let &amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;victim_dexterity &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span&gt;self.components.dexterity.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;get&lt;&#x2F;span&gt;&lt;span&gt;(victim).&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;unwrap&lt;&#x2F;span&gt;&lt;span&gt;();
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; victim_defense_modifier &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span&gt;self.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;defense_modifier&lt;&#x2F;span&gt;&lt;span&gt;(victim);
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; gross_damage &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; attacker_base_damage
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;+&lt;&#x2F;span&gt;&lt;span&gt; rng.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;gen_range&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;..&lt;&#x2F;span&gt;&lt;span&gt;(attacker_strength &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;+ &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;))
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;+&lt;&#x2F;span&gt;&lt;span&gt; attacker_damage_modifier;
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; damage_reduction &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; rng.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;gen_range&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;..&lt;&#x2F;span&gt;&lt;span&gt;(victim_dexterity &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;+ &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;)) &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;+&lt;&#x2F;span&gt;&lt;span&gt; victim_defense_modifier;
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; net_damage &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; gross_damage.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;saturating_sub&lt;&#x2F;span&gt;&lt;span&gt;(damage_reduction).&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;max&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt;) &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;as u32&lt;&#x2F;span&gt;&lt;span&gt;;
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Add a method &lt;code&gt;World::magic&lt;&#x2F;code&gt; which computes a magic score based on intelligence and the magic modifier,
and use this to determine the power of spells.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; world.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;impl &lt;&#x2F;span&gt;&lt;span&gt;World {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;magic&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;self, entity: Entity) -&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;i32 &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;        self.components
&lt;&#x2F;span&gt;&lt;span&gt;            .intelligence
&lt;&#x2F;span&gt;&lt;span&gt;            .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;get&lt;&#x2F;span&gt;&lt;span&gt;(entity)
&lt;&#x2F;span&gt;&lt;span&gt;            .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;cloned&lt;&#x2F;span&gt;&lt;span&gt;()
&lt;&#x2F;span&gt;&lt;span&gt;            .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;unwrap_or&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt;)
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;+ &lt;&#x2F;span&gt;&lt;span&gt;self.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;magic_modifier&lt;&#x2F;span&gt;&lt;span&gt;(entity)
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;pub fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;maybe_use_item_aim&lt;&#x2F;span&gt;&lt;span&gt;(
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut &lt;&#x2F;span&gt;&lt;span&gt;self,
&lt;&#x2F;span&gt;&lt;span&gt;        character: Entity,
&lt;&#x2F;span&gt;&lt;span&gt;        inventory_index: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;usize&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;        target: Coord,
&lt;&#x2F;span&gt;&lt;span&gt;        message_log: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Vec&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;LogMessage&amp;gt;,
&lt;&#x2F;span&gt;&lt;span&gt;    ) -&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Result&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;(), ()&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;match&lt;&#x2F;span&gt;&lt;span&gt; item_type {
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;            ItemType::FireballScroll &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;                &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; fireball &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span&gt;ProjectileType::Fireball {
&lt;&#x2F;span&gt;&lt;span&gt;                    damage: self.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;magic&lt;&#x2F;span&gt;&lt;span&gt;(character).&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;max&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt;) &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;as u32&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;                };
&lt;&#x2F;span&gt;&lt;span&gt;                message_log.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;push&lt;&#x2F;span&gt;&lt;span&gt;(LogMessage::PlayerLaunchesProjectile(fireball));
&lt;&#x2F;span&gt;&lt;span&gt;                self.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;spawn_projectile&lt;&#x2F;span&gt;&lt;span&gt;(character_coord, target, fireball);
&lt;&#x2F;span&gt;&lt;span&gt;            }
&lt;&#x2F;span&gt;&lt;span&gt;            ItemType::ConfusionScroll &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;                &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; confusion &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span&gt;ProjectileType::Confusion {
&lt;&#x2F;span&gt;&lt;span&gt;                    duration: self.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;magic&lt;&#x2F;span&gt;&lt;span&gt;(character).&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;max&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt;) &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;as u32 * &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;3&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;                };
&lt;&#x2F;span&gt;&lt;span&gt;                message_log.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;push&lt;&#x2F;span&gt;&lt;span&gt;(LogMessage::PlayerLaunchesProjectile(confusion));
&lt;&#x2F;span&gt;&lt;span&gt;                self.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;spawn_projectile&lt;&#x2F;span&gt;&lt;span&gt;(character_coord, target, confusion);
&lt;&#x2F;span&gt;&lt;span&gt;            }
&lt;&#x2F;span&gt;&lt;span&gt;        }
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Reference implementation branch: &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;gridbugs&#x2F;chargrid-roguelike-tutorial-2020&#x2F;tree&#x2F;part-13.2&quot;&gt;part-13.2&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;h2 id=&quot;balance-item-distribution&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#balance-item-distribution&quot; aria-label=&quot;Anchor link for: balance-item-distribution&quot;&gt;Balance Item Distribution&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;Replace the debugging values in &lt;code&gt;make_item_probability_distribution&lt;&#x2F;code&gt; with smaller values.
To further reduce the odds of an item being a piece of equipment, increase the probability of
the other items too. Make the odds of finding equipment go up the deeper the player is in the dungeon.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; terrain.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;make_item_probability_distribution&lt;&#x2F;span&gt;&lt;span&gt;(level: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;u32&lt;&#x2F;span&gt;&lt;span&gt;) -&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Vec&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;(ItemType, &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;u32&lt;&#x2F;span&gt;&lt;span&gt;)&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;use &lt;&#x2F;span&gt;&lt;span&gt;ItemType::&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;*&lt;&#x2F;span&gt;&lt;span&gt;;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; item_chance &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= match&lt;&#x2F;span&gt;&lt;span&gt; level {
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;..=&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;1 &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;5&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;2&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;..=&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;3 &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;10&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;_ =&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;20&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;    };
&lt;&#x2F;span&gt;&lt;span&gt;    vec![
&lt;&#x2F;span&gt;&lt;span&gt;        (HealthPotion, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;200&lt;&#x2F;span&gt;&lt;span&gt;),
&lt;&#x2F;span&gt;&lt;span&gt;        (
&lt;&#x2F;span&gt;&lt;span&gt;            FireballScroll,
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;match&lt;&#x2F;span&gt;&lt;span&gt; level {
&lt;&#x2F;span&gt;&lt;span&gt;                &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;..=&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;1 &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;10&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;                &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;2&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;..=&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;4 &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;50&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;                &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;_ =&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;100&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;            },
&lt;&#x2F;span&gt;&lt;span&gt;        ),
&lt;&#x2F;span&gt;&lt;span&gt;        (
&lt;&#x2F;span&gt;&lt;span&gt;            ConfusionScroll,
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;match&lt;&#x2F;span&gt;&lt;span&gt; level {
&lt;&#x2F;span&gt;&lt;span&gt;                &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;..=&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;1 &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;10&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;                &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;2&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;..=&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;4 &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;30&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;                &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;_ =&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;50&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;            },
&lt;&#x2F;span&gt;&lt;span&gt;        ),
&lt;&#x2F;span&gt;&lt;span&gt;        (Sword, item_chance),
&lt;&#x2F;span&gt;&lt;span&gt;        (Staff, item_chance),
&lt;&#x2F;span&gt;&lt;span&gt;        (Armour, item_chance),
&lt;&#x2F;span&gt;&lt;span&gt;        (Robe, item_chance),
&lt;&#x2F;span&gt;&lt;span&gt;    ]
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;These numbers were chosen fairly arbitrarily. Tune these based on the result of play-testing.&lt;&#x2F;p&gt;
&lt;p&gt;Reference implementation branch: &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;gridbugs&#x2F;chargrid-roguelike-tutorial-2020&#x2F;tree&#x2F;part-13.3&quot;&gt;part-13.3&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;h2 id=&quot;log-message-for-equipment&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#log-message-for-equipment&quot; aria-label=&quot;Anchor link for: log-message-for-equipment&quot;&gt;Log Message for Equipment&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;One final touch - adding a log message when the player equips an item.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; game.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;pub enum &lt;&#x2F;span&gt;&lt;span&gt;LogMessage {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;    PlayerEquips(ItemType),
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; world.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;impl &lt;&#x2F;span&gt;&lt;span&gt;World {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;pub fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;maybe_use_item&lt;&#x2F;span&gt;&lt;span&gt;(
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut &lt;&#x2F;span&gt;&lt;span&gt;self,
&lt;&#x2F;span&gt;&lt;span&gt;        character: Entity,
&lt;&#x2F;span&gt;&lt;span&gt;        inventory_index: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;usize&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;        message_log: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Vec&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;LogMessage&amp;gt;,
&lt;&#x2F;span&gt;&lt;span&gt;    ) -&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Result&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;ItemUsage, ()&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; usage &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= match&lt;&#x2F;span&gt;&lt;span&gt; item_type {
&lt;&#x2F;span&gt;&lt;span&gt;            ItemType::HealthPotion &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;                &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;            }
&lt;&#x2F;span&gt;&lt;span&gt;            ItemType::FireballScroll &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;| &lt;&#x2F;span&gt;&lt;span&gt;ItemType::ConfusionScroll &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span&gt;ItemUsage::Aim,
&lt;&#x2F;span&gt;&lt;span&gt;            ItemType::Sword &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;| &lt;&#x2F;span&gt;&lt;span&gt;ItemType::Staff &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;                self.components
&lt;&#x2F;span&gt;&lt;span&gt;                    .equipment_held_inventory_index
&lt;&#x2F;span&gt;&lt;span&gt;                    .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;insert&lt;&#x2F;span&gt;&lt;span&gt;(character, inventory_index);
&lt;&#x2F;span&gt;&lt;span&gt;                message_log.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;push&lt;&#x2F;span&gt;&lt;span&gt;(LogMessage::PlayerEquips(item_type));
&lt;&#x2F;span&gt;&lt;span&gt;                ItemUsage::Immediate
&lt;&#x2F;span&gt;&lt;span&gt;            }
&lt;&#x2F;span&gt;&lt;span&gt;            ItemType::Armour &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;| &lt;&#x2F;span&gt;&lt;span&gt;ItemType::Robe &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;                self.components
&lt;&#x2F;span&gt;&lt;span&gt;                    .equipment_worn_inventory_index
&lt;&#x2F;span&gt;&lt;span&gt;                    .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;insert&lt;&#x2F;span&gt;&lt;span&gt;(character, inventory_index);
&lt;&#x2F;span&gt;&lt;span&gt;                message_log.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;push&lt;&#x2F;span&gt;&lt;span&gt;(LogMessage::PlayerEquips(item_type));
&lt;&#x2F;span&gt;&lt;span&gt;                ItemUsage::Immediate
&lt;&#x2F;span&gt;&lt;span&gt;            }
&lt;&#x2F;span&gt;&lt;span&gt;        };
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; ui.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;impl&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;#39;a&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt; View&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&amp;#39;a &lt;&#x2F;span&gt;&lt;span&gt;[LogMessage]&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;for &lt;&#x2F;span&gt;&lt;span&gt;MessagesView {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;view&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;F: Frame, C: ColModify&amp;gt;(
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut &lt;&#x2F;span&gt;&lt;span&gt;self,
&lt;&#x2F;span&gt;&lt;span&gt;        messages: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&amp;#39;a&lt;&#x2F;span&gt;&lt;span&gt; [LogMessage],
&lt;&#x2F;span&gt;&lt;span&gt;        context: ViewContext&amp;lt;C&amp;gt;,
&lt;&#x2F;span&gt;&lt;span&gt;        frame: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut&lt;&#x2F;span&gt;&lt;span&gt; F,
&lt;&#x2F;span&gt;&lt;span&gt;    ) {
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;format_message&lt;&#x2F;span&gt;&lt;span&gt;(buf: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut&lt;&#x2F;span&gt;&lt;span&gt; [RichTextPartOwned], message: LogMessage) {
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;match&lt;&#x2F;span&gt;&lt;span&gt; message {
&lt;&#x2F;span&gt;&lt;span&gt;                &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;                PlayerEquips(item_type) &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;                    write!(&amp;amp;mut buf[0].text, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;You equip the &amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;).&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;unwrap&lt;&#x2F;span&gt;&lt;span&gt;();
&lt;&#x2F;span&gt;&lt;span&gt;                    write!(&amp;amp;mut buf[1].text, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;{}&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;, item_type.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;name&lt;&#x2F;span&gt;&lt;span&gt;()).&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;unwrap&lt;&#x2F;span&gt;&lt;span&gt;();
&lt;&#x2F;span&gt;&lt;span&gt;                    buf[&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;].style.foreground &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Some&lt;&#x2F;span&gt;&lt;span&gt;(colours::item_colour(item_type));
&lt;&#x2F;span&gt;&lt;span&gt;                    write!(&amp;amp;mut buf[2].text, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;.&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;).&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;unwrap&lt;&#x2F;span&gt;&lt;span&gt;();
&lt;&#x2F;span&gt;&lt;span&gt;                }
&lt;&#x2F;span&gt;&lt;span&gt;            }
&lt;&#x2F;span&gt;&lt;span&gt;        }
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;roguelike-tutorial-2020-part-13&#x2F;message.png&quot; alt=&quot;message.png&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;That concludes the tutorial series. There’s still a lot more work to do before this game can be considered complete.
It has no ending, and is very light on content and mechanics. Hopefully by now you have enough of a handle on
&lt;code&gt;chargrid&lt;&#x2F;code&gt; that you can extend the project we made here into the roguelike of your dreams.&lt;&#x2F;p&gt;
&lt;p&gt;Reference implementation branch: &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;gridbugs&#x2F;chargrid-roguelike-tutorial-2020&#x2F;tree&#x2F;part-13.4&quot;&gt;part-13.4&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Part 12 - Increasing Difficulty</title>
          <pubDate>Sat, 15 Aug 2020 19:30:00 +1000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/roguelike-tutorial-2020-part-12/</link>
          <guid>https://www.gridbugs.org/roguelike-tutorial-2020-part-12/</guid>
          <description xml:base="https://www.gridbugs.org/roguelike-tutorial-2020-part-12/">&lt;p&gt;In this part we’ll update terrain generation logic such that the game gets
more difficult the deeper you descend into the dungeon.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;roguelike-tutorial-2020-part-12&#x2F;screenshot-end.png&quot; alt=&quot;screenshot-end.png&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;This part is loosely based on &lt;a href=&quot;http:&#x2F;&#x2F;rogueliketutorials.com&#x2F;tutorials&#x2F;tcod&#x2F;part-12&#x2F;&quot;&gt;this part&lt;&#x2F;a&gt; of the
python tcod tutorial.&lt;&#x2F;p&gt;
&lt;p&gt;Reference implementation branch for starting point: &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;gridbugs&#x2F;chargrid-roguelike-tutorial-2020&#x2F;tree&#x2F;part-11-end&quot;&gt;part-11-end&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;p&gt;We’ll start by replacing the logic for choosing which NPC and item to spawn with a generic probability distribution.
This will make it easier to increase the chance of spawning more dangerous NPCs and more powerful items, the deeper
the player descends. It will also make it easier to balance the game later, as the logic for placing NPCs and items
will be separate from the specification of how likely each NPC and item is to spawn.&lt;&#x2F;p&gt;
&lt;p&gt;Add this function to &lt;code&gt;terrain.rs&lt;&#x2F;code&gt;. It takes a probability distribution represented by a slice of pairs of values and numbers,
where the relative size of numbers determines the chance that a value is chosen.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; terrain.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;choose_from_probability_distribution&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;#39;a&lt;&#x2F;span&gt;&lt;span&gt;, T, R: Rng&amp;gt;(
&lt;&#x2F;span&gt;&lt;span&gt;    probability_distribution: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&amp;#39;a&lt;&#x2F;span&gt;&lt;span&gt; [(T, u32)],
&lt;&#x2F;span&gt;&lt;span&gt;    rng: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut&lt;&#x2F;span&gt;&lt;span&gt; R,
&lt;&#x2F;span&gt;&lt;span&gt;) -&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&amp;#39;a&lt;&#x2F;span&gt;&lt;span&gt; T {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; sum &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; probability_distribution.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;iter&lt;&#x2F;span&gt;&lt;span&gt;().&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;map&lt;&#x2F;span&gt;&lt;span&gt;(|(_, p)| p).sum::&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;u32&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;();
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;let mut&lt;&#x2F;span&gt;&lt;span&gt; choice &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; rng.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;gen_range&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;..&lt;&#x2F;span&gt;&lt;span&gt;sum);
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;for &lt;&#x2F;span&gt;&lt;span&gt;(value, probability) &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;in&lt;&#x2F;span&gt;&lt;span&gt; probability_distribution.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;iter&lt;&#x2F;span&gt;&lt;span&gt;() {
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;if let &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Some&lt;&#x2F;span&gt;&lt;span&gt;(remaining_choice) &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; choice.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;checked_sub&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;*&lt;&#x2F;span&gt;&lt;span&gt;probability) {
&lt;&#x2F;span&gt;&lt;span&gt;            choice &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; remaining_choice;
&lt;&#x2F;span&gt;&lt;span&gt;        } &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;else &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;return&lt;&#x2F;span&gt;&lt;span&gt; value;
&lt;&#x2F;span&gt;&lt;span&gt;        }
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;    unreachable!()
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Add functions which generate probability distributions for NPCs and items.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; terrain.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;make_npc_probability_distribution&lt;&#x2F;span&gt;&lt;span&gt;(level: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;u32&lt;&#x2F;span&gt;&lt;span&gt;) -&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Vec&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;(NpcType, &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;u32&lt;&#x2F;span&gt;&lt;span&gt;)&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;use &lt;&#x2F;span&gt;&lt;span&gt;NpcType::&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;*&lt;&#x2F;span&gt;&lt;span&gt;;
&lt;&#x2F;span&gt;&lt;span&gt;    vec![(Orc, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;20&lt;&#x2F;span&gt;&lt;span&gt;), (Troll, level)]
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;make_item_probability_distribution&lt;&#x2F;span&gt;&lt;span&gt;(level: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;u32&lt;&#x2F;span&gt;&lt;span&gt;) -&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Vec&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;(ItemType, &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;u32&lt;&#x2F;span&gt;&lt;span&gt;)&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;use &lt;&#x2F;span&gt;&lt;span&gt;ItemType::&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;*&lt;&#x2F;span&gt;&lt;span&gt;;
&lt;&#x2F;span&gt;&lt;span&gt;    vec![
&lt;&#x2F;span&gt;&lt;span&gt;        (HealthPotion, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;20&lt;&#x2F;span&gt;&lt;span&gt;),
&lt;&#x2F;span&gt;&lt;span&gt;        (
&lt;&#x2F;span&gt;&lt;span&gt;            FireballScroll,
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;match&lt;&#x2F;span&gt;&lt;span&gt; level {
&lt;&#x2F;span&gt;&lt;span&gt;                &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;..=&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;1 &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;                &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;2&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;..=&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;4 &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;5&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;                &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;_ =&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;10&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;            },
&lt;&#x2F;span&gt;&lt;span&gt;        ),
&lt;&#x2F;span&gt;&lt;span&gt;        (
&lt;&#x2F;span&gt;&lt;span&gt;            ConfusionScroll,
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;match&lt;&#x2F;span&gt;&lt;span&gt; level {
&lt;&#x2F;span&gt;&lt;span&gt;                &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;..=&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;1 &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;                &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;2&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;..=&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;4 &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;3&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;                &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;_ =&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;5&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;            },
&lt;&#x2F;span&gt;&lt;span&gt;        ),
&lt;&#x2F;span&gt;&lt;span&gt;    ]
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;During play-testing, these are values that can be tweaked to adjust the difficulty of the game.&lt;&#x2F;p&gt;
&lt;p&gt;Update &lt;code&gt;Room::place_npcs&lt;&#x2F;code&gt; and &lt;code&gt;Room::place_items&lt;&#x2F;code&gt; to use generic probability distributions.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; terrain.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;impl &lt;&#x2F;span&gt;&lt;span&gt;Room {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; Place `n` randomly chosen NPCs at random positions within the room
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;place_npcs&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;R: Rng&amp;gt;(
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;self,
&lt;&#x2F;span&gt;&lt;span&gt;        n: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;usize&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;        probability_distribution: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;[(NpcType, u32)],
&lt;&#x2F;span&gt;&lt;span&gt;        grid: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut &lt;&#x2F;span&gt;&lt;span&gt;Grid&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Option&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;TerrainTile&amp;gt;&amp;gt;,
&lt;&#x2F;span&gt;&lt;span&gt;        rng: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut&lt;&#x2F;span&gt;&lt;span&gt; R,
&lt;&#x2F;span&gt;&lt;span&gt;    ) {
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;for&lt;&#x2F;span&gt;&lt;span&gt; coord &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;in &lt;&#x2F;span&gt;&lt;span&gt;self
&lt;&#x2F;span&gt;&lt;span&gt;            .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;coords&lt;&#x2F;span&gt;&lt;span&gt;()
&lt;&#x2F;span&gt;&lt;span&gt;            .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;filter&lt;&#x2F;span&gt;&lt;span&gt;(|&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;coord| grid.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;get_checked&lt;&#x2F;span&gt;&lt;span&gt;(coord).&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;unwrap&lt;&#x2F;span&gt;&lt;span&gt;() &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;== &lt;&#x2F;span&gt;&lt;span&gt;TerrainTile::Floor)
&lt;&#x2F;span&gt;&lt;span&gt;            .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;choose_multiple&lt;&#x2F;span&gt;&lt;span&gt;(rng, n)
&lt;&#x2F;span&gt;&lt;span&gt;        {
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;let &amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;npc_type &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;choose_from_probability_distribution&lt;&#x2F;span&gt;&lt;span&gt;(probability_distribution, rng);
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;*&lt;&#x2F;span&gt;&lt;span&gt;grid.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;get_checked_mut&lt;&#x2F;span&gt;&lt;span&gt;(coord) &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Some&lt;&#x2F;span&gt;&lt;span&gt;(TerrainTile::Npc(npc_type));
&lt;&#x2F;span&gt;&lt;span&gt;        }
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; Place `n` items at random positions within the room
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;place_items&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;R: Rng&amp;gt;(
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;self,
&lt;&#x2F;span&gt;&lt;span&gt;        n: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;usize&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;        probability_distribution: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;[(ItemType, u32)],
&lt;&#x2F;span&gt;&lt;span&gt;        grid: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut &lt;&#x2F;span&gt;&lt;span&gt;Grid&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Option&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;TerrainTile&amp;gt;&amp;gt;,
&lt;&#x2F;span&gt;&lt;span&gt;        rng: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut&lt;&#x2F;span&gt;&lt;span&gt; R,
&lt;&#x2F;span&gt;&lt;span&gt;    ) {
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;for&lt;&#x2F;span&gt;&lt;span&gt; coord &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;in &lt;&#x2F;span&gt;&lt;span&gt;self
&lt;&#x2F;span&gt;&lt;span&gt;            .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;coords&lt;&#x2F;span&gt;&lt;span&gt;()
&lt;&#x2F;span&gt;&lt;span&gt;            .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;filter&lt;&#x2F;span&gt;&lt;span&gt;(|&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;coord| grid.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;get_checked&lt;&#x2F;span&gt;&lt;span&gt;(coord).&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;unwrap&lt;&#x2F;span&gt;&lt;span&gt;() &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;== &lt;&#x2F;span&gt;&lt;span&gt;TerrainTile::Floor)
&lt;&#x2F;span&gt;&lt;span&gt;            .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;choose_multiple&lt;&#x2F;span&gt;&lt;span&gt;(rng, n)
&lt;&#x2F;span&gt;&lt;span&gt;        {
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;let &amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;item &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;choose_from_probability_distribution&lt;&#x2F;span&gt;&lt;span&gt;(probability_distribution, rng);
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;*&lt;&#x2F;span&gt;&lt;span&gt;grid.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;get_checked_mut&lt;&#x2F;span&gt;&lt;span&gt;(coord) &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Some&lt;&#x2F;span&gt;&lt;span&gt;(TerrainTile::Item(item));
&lt;&#x2F;span&gt;&lt;span&gt;        }
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Update &lt;code&gt;generate_dungeon&lt;&#x2F;code&gt; to create probability distributions.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; terrain.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;pub fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;generate_dungeon&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;R: Rng&amp;gt;(size: Size, level: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;u32&lt;&#x2F;span&gt;&lt;span&gt;, rng: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut&lt;&#x2F;span&gt;&lt;span&gt; R) -&amp;gt; Grid&amp;lt;TerrainTile&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; npc_probability_distribution &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;make_npc_probability_distribution&lt;&#x2F;span&gt;&lt;span&gt;(level);
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; item_probability_distribution &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;make_item_probability_distribution&lt;&#x2F;span&gt;&lt;span&gt;(level);
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; Attempt to add a room a constant number of times
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;const &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;NUM_ATTEMPTS&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;usize = &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;100&lt;&#x2F;span&gt;&lt;span&gt;;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;for _ in &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;..&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;NUM_ATTEMPTS &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;if&lt;&#x2F;span&gt;&lt;span&gt; room.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;only_intersects_empty&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;grid) {
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; Add npcs to the room
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;let &amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;num_npcs &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;NPCS_PER_ROOM_DISTRIBUTION&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;choose&lt;&#x2F;span&gt;&lt;span&gt;(rng).&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;unwrap&lt;&#x2F;span&gt;&lt;span&gt;();
&lt;&#x2F;span&gt;&lt;span&gt;            room.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;place_npcs&lt;&#x2F;span&gt;&lt;span&gt;(num_npcs, &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;npc_probability_distribution, &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut&lt;&#x2F;span&gt;&lt;span&gt; grid, rng);
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; Add items to the room
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;let &amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;num_items &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;ITEMS_PER_ROOM_DISTRIBUTION&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;choose&lt;&#x2F;span&gt;&lt;span&gt;(rng).&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;unwrap&lt;&#x2F;span&gt;&lt;span&gt;();
&lt;&#x2F;span&gt;&lt;span&gt;            room.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;place_items&lt;&#x2F;span&gt;&lt;span&gt;(num_items, &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;item_probability_distribution, &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut&lt;&#x2F;span&gt;&lt;span&gt; grid, rng);
&lt;&#x2F;span&gt;&lt;span&gt;        }
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Update &lt;code&gt;world.rs&lt;&#x2F;code&gt; to pass a level to &lt;code&gt;generate_dungeon&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;impl &lt;&#x2F;span&gt;&lt;span&gt;World {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;pub fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;populate&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;R: Rng&amp;gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut &lt;&#x2F;span&gt;&lt;span&gt;self, level: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;u32&lt;&#x2F;span&gt;&lt;span&gt;, rng: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut&lt;&#x2F;span&gt;&lt;span&gt; R) -&amp;gt; Populate {
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; terrain &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span&gt;terrain::generate_dungeon(self.spatial_table.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;grid_size&lt;&#x2F;span&gt;&lt;span&gt;(), level, rng);
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;And update &lt;code&gt;game.rs&lt;&#x2F;code&gt; to pass the current level to &lt;code&gt;World::populate&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;impl &lt;&#x2F;span&gt;&lt;span&gt;GameState {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;pub fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;new&lt;&#x2F;span&gt;&lt;span&gt;(
&lt;&#x2F;span&gt;&lt;span&gt;        screen_size: Size,
&lt;&#x2F;span&gt;&lt;span&gt;        rng_seed: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;u64&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;        initial_visibility_algorithm: VisibilityAlgorithm,
&lt;&#x2F;span&gt;&lt;span&gt;    ) -&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;Self &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; dungeon_level &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;;
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; Populate {
&lt;&#x2F;span&gt;&lt;span&gt;            player_entity,
&lt;&#x2F;span&gt;&lt;span&gt;            ai_state,
&lt;&#x2F;span&gt;&lt;span&gt;        } &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; world.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;populate&lt;&#x2F;span&gt;&lt;span&gt;(dungeon_level, &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut&lt;&#x2F;span&gt;&lt;span&gt; rng);
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;pub fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;player_level_up_and_descend&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut &lt;&#x2F;span&gt;&lt;span&gt;self, level_up: LevelUp) {
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;        self.dungeon_level &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;+= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;;
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; Populate {
&lt;&#x2F;span&gt;&lt;span&gt;            player_entity,
&lt;&#x2F;span&gt;&lt;span&gt;            ai_state,
&lt;&#x2F;span&gt;&lt;span&gt;        } &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span&gt;self.world.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;populate&lt;&#x2F;span&gt;&lt;span&gt;(self.dungeon_level, &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut &lt;&#x2F;span&gt;&lt;span&gt;self.rng);
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Now play the game a bunch and tweak the probabilities of NPCs and items until it’s fun.
There’s one more part after this one, in which we’ll add equipment.&lt;&#x2F;p&gt;
&lt;p&gt;Reference implementation branch: &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;gridbugs&#x2F;chargrid-roguelike-tutorial-2020&#x2F;tree&#x2F;part-12.0&quot;&gt;part-12.0&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;a href=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;roguelike-tutorial-2020-part-13&#x2F;&quot;&gt;Click here for the next part!&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Chargrid Roguelike Tutorial 2020</title>
          <pubDate>Sat, 15 Aug 2020 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/daily/chargrid-roguelike-tutorial-2020/</link>
          <guid>https://www.gridbugs.org/daily/chargrid-roguelike-tutorial-2020/</guid>
          <description xml:base="https://www.gridbugs.org/daily/chargrid-roguelike-tutorial-2020/">&lt;p&gt;Today I finished writing the code and tutorial pages for the tutorial I’m making
as part of
&lt;a href=&quot;https:&#x2F;&#x2F;old.reddit.com&#x2F;r&#x2F;roguelikedev&#x2F;wiki&#x2F;python_tutorial_series&quot;&gt;roguelikedev subreddit does the complete
roguelike tutorial&lt;&#x2F;a&gt;.
The tutorial pages can be read &lt;a href=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;roguelike-tutorial-2020&#x2F;&quot;&gt;here&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;I’ve been working on this in my spare time for about 8 weeks. Unlike the 7drl,
for this project I started from nothing and built the entire engine and game using
a collection of rust libraries I’ve made. Also unlike the 7drl, my aim was to make a vanilla
procedurally-generated dungeon crawler, with no interesting features, to demonstrate
how my libraries can be used to implement a roguelike.&lt;&#x2F;p&gt;
&lt;p&gt;The biggest personal benefit of doing this project has been that I was forced to explain
all the concepts of my libraries to someone other than myself. This lead to various simplifications
and quality of life improvements. I also noticed a common pattern I’ve used in my most recent roguelike engines
(namely &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;gridbugs&#x2F;rip&quot;&gt;rip&lt;&#x2F;a&gt; and &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;gridbugs&#x2F;slime99&#x2F;&quot;&gt;slime99&lt;&#x2F;a&gt;)
which is generic and internally complex. Not wanting to explain its inner workings as part of the
tutorial, I factored it out into the &lt;a href=&quot;https:&#x2F;&#x2F;crates.io&#x2F;crates&#x2F;spatial_table&quot;&gt;spatial_table&lt;&#x2F;a&gt; crate.&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Problems, not Solutions</title>
          <pubDate>Fri, 14 Aug 2020 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/daily/problems-not-solutions/</link>
          <guid>https://www.gridbugs.org/daily/problems-not-solutions/</guid>
          <description xml:base="https://www.gridbugs.org/daily/problems-not-solutions/">&lt;p&gt;A few years ago, at a past job, I received a piece of advice from a code reviewer which I’ve treasured every since:
“describe a change in terms of the problem it addresses, rather than the solution it implements”.&lt;&#x2F;p&gt;
&lt;p&gt;Focusing on solutions is tempting! You’re a smart engineer. You worked hard on your solution,
and you’re proud of the code you wrote. When you’ve spent a few days working on implementing a new
feature, it’s easy to just describe the work you did.&lt;&#x2F;p&gt;
&lt;p&gt;The trouble with this approach is that
reviewers, and people who later find your code with (say) git blame, won’t understand
why the change was made. The change will exist as a technical artifact, and will be reviewed as
such, but stripping away the context as to why the change was made means some difficult questions
might not get asked during review. And after the code is checked in, if someone besides the author
thinks the code should be changed further, or even removed, it takes more effort if the “paper trail”
associated with a particular piece of code doesn’t include the reason it was written in the first place.&lt;&#x2F;p&gt;
&lt;p&gt;Take this idea further. When someone tries to convince you of an idea, sell you a product, pitch you
a company, it’s common for the person with a solution to focus on the shiny solution. It’s common for the
person on the receiving end to be impressed by a shiny solution, and skip the part where they
question whether it’s a problem that needs solving (at least right now), or whether the solution is appropriate.&lt;&#x2F;p&gt;
&lt;p&gt;Beware of solutions without problems.&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>BOOTBOOT</title>
          <pubDate>Thu, 13 Aug 2020 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/daily/bootboot/</link>
          <guid>https://www.gridbugs.org/daily/bootboot/</guid>
          <description xml:base="https://www.gridbugs.org/daily/bootboot/">&lt;p&gt;A month or so ago I started following &lt;a href=&quot;https:&#x2F;&#x2F;os.phil-opp.com&#x2F;first-edition&#x2F;&quot;&gt;the first edition of writing an OS in rust&lt;&#x2F;a&gt;
which is a tutorial for writing an OS in rust.
I tried to get it working on my FreeBSD laptop and ran into what I’ll call “yet another teething problem
for me as a new FreeBSD user”. I’d been using a tool called &lt;code&gt;grub-mkrescue&lt;&#x2F;code&gt; to build a bootable cdrom image
containing the grub bootloader and an image of the kernel we build in the tutorial.
Grub is not available on FreeBSD (though hunting for help on this issue indicates that it &lt;em&gt;used&lt;&#x2F;em&gt; to be).&lt;&#x2F;p&gt;
&lt;p&gt;There is a pure rust bootloader in a crate called &lt;a href=&quot;https:&#x2F;&#x2F;crates.io&#x2F;crates&#x2F;bootloader&quot;&gt;bootloader&lt;&#x2F;a&gt;.
The &lt;em&gt;second&lt;&#x2F;em&gt; edition of the tutorial I’m following uses it (in fact it was written just for the tutorial).
I’ve avoided it thus far as I want a more general - not rust-specific - understanding of the boot process.&lt;&#x2F;p&gt;
&lt;p&gt;An alternative that I just discovered is &lt;a href=&quot;https:&#x2F;&#x2F;wiki.osdev.org&#x2F;BOOTBOOT&quot;&gt;BOOTBOOT&lt;&#x2F;a&gt; which is a
bootloader protocol and reference implementation. It does a little more setup than grub.
Kernels begin executing in long mode, and the bootloader initializes a framebuffer and serial console.
I’d honestly rather it didn’t, preferring to work out that myself, but it seems I’m out of options.
Initial setup isn’t the most interesting part of OS dev.
I’ve gone through the &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;gridbugs&#x2F;writing-an-os-in-rust&#x2F;blob&#x2F;master&#x2F;src&#x2F;boot.asm&quot;&gt;ritualistic process&lt;&#x2F;a&gt;
of transitioning from protected mode to long mode on x86. I’m glad to have done that once, and once is probably enough.&lt;&#x2F;p&gt;
&lt;p&gt;Grub is large and complicated and does far more than I need it to do. BOOTBOOT comes with a tool &lt;code&gt;mkbootimg&lt;&#x2F;code&gt;
which performs a similar function to &lt;code&gt;grub-mkrescue&lt;&#x2F;code&gt; - making a bootable disk image containing a bootloader
and a kernel. What I love about this tool is that’s &lt;em&gt;all&lt;&#x2F;em&gt; it does.&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Sticking with FreeBSD for now</title>
          <pubDate>Wed, 12 Aug 2020 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/daily/sticking-with-freebsd-for-now/</link>
          <guid>https://www.gridbugs.org/daily/sticking-with-freebsd-for-now/</guid>
          <description xml:base="https://www.gridbugs.org/daily/sticking-with-freebsd-for-now/">&lt;p&gt;I’m going to stop boiling the ocean in an attempt to run OpenBSD on my laptop
and accept that the tools that I need to be productive in my personal projects
currently don’t support OpenBSD. The one tool in question is &lt;a href=&quot;https:&#x2F;&#x2F;rustup.rs&#x2F;&quot;&gt;rustup&lt;&#x2F;a&gt;,
which makes it &lt;em&gt;convenient&lt;&#x2F;em&gt; to manage multiple concurrent rust insallations (e.g. stable for most things
and nightly for the rest), but it’s all but &lt;em&gt;essential&lt;&#x2F;em&gt; to set up cross compilation
toolchains, which I especially need for compiling rust to web assembly.
I &lt;em&gt;could&lt;&#x2F;em&gt; continue down my current path of building the rust compiler and rustup
from source, and convincing rustup to manage toolchains even though the compiler
binaries aren’t even available for OpenBSD. After starting down this route, I’ve decided
that it’s more effort than it’s worth right now, and my setup wouldn’t be as reliable
as it would on (say) FreeBSD, as I’d be using a bunch of tools in a way which doesn’t have the
“blessing” of their maintainers, which means I should expect occasional breakages.
Not to mention that the &lt;em&gt;only&lt;&#x2F;em&gt; way to be productive on rust is to run the -current branch
of OpenBSD, (for an up-to-date rust package) which is itself subject to occasional breakages.&lt;&#x2F;p&gt;
&lt;p&gt;In the ideal world, I’d run -stable OpenBSD and use rustup to manage a rust installation.
In the meantime I will keep an eye on rust platform support, and as soon as OpenBSD gains
&lt;a href=&quot;https:&#x2F;&#x2F;forge.rust-lang.org&#x2F;release&#x2F;platform-support.html#tier-2&quot;&gt;tier 2&lt;&#x2F;a&gt; I’ll have another
go at switching to it as a daily driver.&lt;&#x2F;p&gt;
&lt;p&gt;This experiment has taught me a great deal. As a result, my &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;gridbugs&#x2F;dotfiles&quot;&gt;dotfiles repository&lt;&#x2F;a&gt;
(a collection of config files I maintain) has become more portable, and I’ve re-evaluated
my dependencies on language managers and removed most of them (all but rustup!).
OpenBSD is the only OS that I’ve installed on my laptop (a lenovo thinkpad t470) where the
“out of the box” functionality included a working trackpad, wifi, and lcd backlight (with the exception of windows
which I don’t think I ever actually booted up).&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Bootstrapping Nightly Rust on OpenBSD</title>
          <pubDate>Tue, 11 Aug 2020 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/daily/bootstrapping-nightly-rust-on-openbsd/</link>
          <guid>https://www.gridbugs.org/daily/bootstrapping-nightly-rust-on-openbsd/</guid>
          <description xml:base="https://www.gridbugs.org/daily/bootstrapping-nightly-rust-on-openbsd/">&lt;p&gt;I underestimated my requirements of a rust compiler:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;I occasionally need to use the nightly compiler rather than the stable compiler to enable experimental features&lt;&#x2F;li&gt;
&lt;li&gt;I need to be able to install the web assembly target for rust so I can compile my games to web assembly&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;These two tasks are both handled by &lt;a href=&quot;https:&#x2F;&#x2F;rustup.rs&#x2F;&quot;&gt;rustup&lt;&#x2F;a&gt;.
Frustratingly, when researching how to live without this rustup, &lt;a href=&quot;https:&#x2F;&#x2F;www.reddit.com&#x2F;r&#x2F;rust&#x2F;comments&#x2F;9skiyi&#x2F;wasm_without_rustup&#x2F;&quot;&gt;the response is often to just use rustup&lt;&#x2F;a&gt;.
This is a massive pet peeve of mine.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;rust-lang&#x2F;rustup&#x2F;issues&#x2F;2168&quot;&gt;But there is hope!&lt;&#x2F;a&gt;
Using rustup to add targets (such as web assembly) seems like it’s still possible on OpenBSD,
even if rustup can’t be used to install the compiler itself.&lt;&#x2F;p&gt;
&lt;p&gt;One of the maintainers of the OpenBSD rust port has made
&lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;semarie&#x2F;build-rust&quot;&gt;this script&lt;&#x2F;a&gt; for bootstrapping the nightly toolchain
from OpenBSD’s rust package (via the beta toolchain).
A caveat is that you must be running OpenBSD -current, as &lt;a href=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;daily&#x2F;rust-on-openbsd&#x2F;&quot;&gt;as I discovered yesterday&lt;&#x2F;a&gt;,
the rust package on -stable is too out-of-date to build the rust compiler.
I’ve re-purposed my old gaming PC as a build server, and I’m compiling rust from source now!&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Rust on OpenBSD</title>
          <pubDate>Mon, 10 Aug 2020 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/daily/rust-on-openbsd/</link>
          <guid>https://www.gridbugs.org/daily/rust-on-openbsd/</guid>
          <description xml:base="https://www.gridbugs.org/daily/rust-on-openbsd/">&lt;p&gt;Rust projects tend to start using new language features as soon as they land in the stable compiler.
The rust compiler gets an update once per month, often adding new language features.
OpenBSD releases a new version every six months, and packages do not gain feature updates outside
of this release schedule (only security updates and bug fixes).
The implication of this is that most of the time, the rust compiler from OpenBSD’s package repository
is too out-of-date to build most rust packages.&lt;&#x2F;p&gt;
&lt;p&gt;On pretty much any other system, the solution would be to just install rust with &lt;a href=&quot;https:&#x2F;&#x2F;rustup.rs&#x2F;&quot;&gt;rustup&lt;&#x2F;a&gt;,
but rust doesn’t make binaries available for OpenBSD, so that won’t work here.
I could compile the rust compiler from source, but as the rust compiler is written in rust, and like most
rust projects, depends on recently-added language features, I’ll need a fairly up-to-date rust compiler
to bootstrap the compiler from source, and the one in the package repo is till too old.&lt;&#x2F;p&gt;
&lt;p&gt;I sought help on the OpenBSD subreddit, and the advice I got was to switch to the “-current” branch
of OpenBSD, which takes the base system and packages from developer snapshots, rather than the biannual
stable release. I ran archlinux for the better part of a decade, so I’m not phased by this!
I followed the advice and everything has worked fine so far.&lt;&#x2F;p&gt;
&lt;p&gt;This means that the only issue preventing me from running OpenBSD on my personal laptop is a
lack of support for OpenBSD’s audio system in the &lt;a href=&quot;https:&#x2F;&#x2F;crates.io&#x2F;crates&#x2F;cpal&quot;&gt;cpal&lt;&#x2F;a&gt; rust
library. I need this because I make games in rust, and cpal appears to be the de-facto standard
way to play audio in rust, and I used it for audio in my game engine.
Now that I have a working rust compiler, I can start working on
adding OpenBSD support to cpal myself.&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Part 11 - Descending the Stairs</title>
          <pubDate>Sun, 09 Aug 2020 21:00:00 +1000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/roguelike-tutorial-2020-part-11/</link>
          <guid>https://www.gridbugs.org/roguelike-tutorial-2020-part-11/</guid>
          <description xml:base="https://www.gridbugs.org/roguelike-tutorial-2020-part-11/">&lt;p&gt;In this part we’ll add stairs, and multiple dungeon levels, as well as the ability to upgrade the
player character as they descend the stairs.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;roguelike-tutorial-2020-part-11&#x2F;screenshot-end.png&quot; alt=&quot;screenshot-end.png&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;This part is loosely based on &lt;a href=&quot;http:&#x2F;&#x2F;rogueliketutorials.com&#x2F;tutorials&#x2F;tcod&#x2F;part-11&#x2F;&quot;&gt;this part&lt;&#x2F;a&gt; of the
python tcod tutorial.&lt;&#x2F;p&gt;
&lt;p&gt;Reference implementation branch for starting point: &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;gridbugs&#x2F;chargrid-roguelike-tutorial-2020&#x2F;tree&#x2F;part-10-end&quot;&gt;part-10-end&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;p&gt;In this post:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;roguelike-tutorial-2020-part-11&#x2F;#placing-stairs&quot;&gt;Placing Stairs&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;roguelike-tutorial-2020-part-11&#x2F;#descending-stairs&quot;&gt;Descending Stairs&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;roguelike-tutorial-2020-part-11&#x2F;#add-combat-stats&quot;&gt;Add Combat Stats&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;roguelike-tutorial-2020-part-11&#x2F;#use-combat-stats&quot;&gt;Use Combat Stats&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;roguelike-tutorial-2020-part-11&#x2F;#upgrade-when-descending-stairs&quot;&gt;Upgrade when Descending Stairs&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;placing-stairs&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#placing-stairs&quot; aria-label=&quot;Anchor link for: placing-stairs&quot;&gt;Placing Stairs&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;Start by adding an entity to each floor representing the stairs to the next floor down.
For this game, stairs only lead downwards, and there’s no way to get back to earlier
floors of the dungeon.&lt;&#x2F;p&gt;
&lt;p&gt;Update the terrain generator to add stairs to the last room it creates.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; terrain.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;pub enum &lt;&#x2F;span&gt;&lt;span&gt;TerrainTile {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;    Stairs,
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;pub fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;generate_dungeon&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;R: Rng&amp;gt;(size: Size, rng: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut&lt;&#x2F;span&gt;&lt;span&gt; R) -&amp;gt; Grid&amp;lt;TerrainTile&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;let mut&lt;&#x2F;span&gt;&lt;span&gt; grid &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span&gt;Grid::new_copy(size, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;None&lt;&#x2F;span&gt;&lt;span&gt;);
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;let mut&lt;&#x2F;span&gt;&lt;span&gt; room_centres &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Vec&lt;&#x2F;span&gt;&lt;span&gt;::new();
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; Add stairs to the centre of the last room placed
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;*&lt;&#x2F;span&gt;&lt;span&gt;grid.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;get_checked_mut&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;*&lt;&#x2F;span&gt;&lt;span&gt;room_centres.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;last&lt;&#x2F;span&gt;&lt;span&gt;().&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;unwrap&lt;&#x2F;span&gt;&lt;span&gt;()) &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Some&lt;&#x2F;span&gt;&lt;span&gt;(TerrainTile::Stairs);
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    grid.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;map&lt;&#x2F;span&gt;&lt;span&gt;(|t| t.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;unwrap_or&lt;&#x2F;span&gt;&lt;span&gt;(TerrainTile::Wall))
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Add a stairs tile, a stairs component, and spawn stairs according to the map produced by terrain generation.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; world.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;pub enum &lt;&#x2F;span&gt;&lt;span&gt;Tile {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;    Stairs,
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;entity_table::declare_entity_module&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;! &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;    components {
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;        stairs: (),
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;impl &lt;&#x2F;span&gt;&lt;span&gt;World {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;spawn_stairs&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut &lt;&#x2F;span&gt;&lt;span&gt;self, coord: Coord) {
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; entity &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span&gt;self.entity_allocator.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;alloc&lt;&#x2F;span&gt;&lt;span&gt;();
&lt;&#x2F;span&gt;&lt;span&gt;        self.spatial_table
&lt;&#x2F;span&gt;&lt;span&gt;            .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;update&lt;&#x2F;span&gt;&lt;span&gt;(
&lt;&#x2F;span&gt;&lt;span&gt;                entity,
&lt;&#x2F;span&gt;&lt;span&gt;                Location {
&lt;&#x2F;span&gt;&lt;span&gt;                    coord,
&lt;&#x2F;span&gt;&lt;span&gt;                    layer: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Some&lt;&#x2F;span&gt;&lt;span&gt;(Layer::Floor),
&lt;&#x2F;span&gt;&lt;span&gt;                },
&lt;&#x2F;span&gt;&lt;span&gt;            )
&lt;&#x2F;span&gt;&lt;span&gt;            .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;unwrap&lt;&#x2F;span&gt;&lt;span&gt;();
&lt;&#x2F;span&gt;&lt;span&gt;        self.components.tile.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;insert&lt;&#x2F;span&gt;&lt;span&gt;(entity, Tile::Stairs);
&lt;&#x2F;span&gt;&lt;span&gt;        self.components.stairs.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;insert&lt;&#x2F;span&gt;&lt;span&gt;(entity, ());
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;pub fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;populate&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;R: Rng&amp;gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut &lt;&#x2F;span&gt;&lt;span&gt;self, rng: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut&lt;&#x2F;span&gt;&lt;span&gt; R) -&amp;gt; Populate {
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;for &lt;&#x2F;span&gt;&lt;span&gt;(coord, &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;terrain_tile) &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;in&lt;&#x2F;span&gt;&lt;span&gt; terrain.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;enumerate&lt;&#x2F;span&gt;&lt;span&gt;() {
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;match&lt;&#x2F;span&gt;&lt;span&gt; terrain_tile {
&lt;&#x2F;span&gt;&lt;span&gt;                &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;                TerrainTile::Stairs &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span&gt;self.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;spawn_stairs&lt;&#x2F;span&gt;&lt;span&gt;(coord),
&lt;&#x2F;span&gt;&lt;span&gt;                &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;            }
&lt;&#x2F;span&gt;&lt;span&gt;        }
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Add a case to the renderer to handle stairs.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; app.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;currently_visible_view_cell_of_tile&lt;&#x2F;span&gt;&lt;span&gt;(tile: Tile) -&amp;gt; ViewCell {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;match&lt;&#x2F;span&gt;&lt;span&gt; tile {
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;        Tile::Stairs &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span&gt;ViewCell::new()
&lt;&#x2F;span&gt;&lt;span&gt;            .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;with_character&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;#39;&amp;gt;&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;)
&lt;&#x2F;span&gt;&lt;span&gt;            .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;with_bold&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;true&lt;&#x2F;span&gt;&lt;span&gt;)
&lt;&#x2F;span&gt;&lt;span&gt;            .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;with_foreground&lt;&#x2F;span&gt;&lt;span&gt;(Rgb24::new_grey(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;255&lt;&#x2F;span&gt;&lt;span&gt;))
&lt;&#x2F;span&gt;&lt;span&gt;            .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;with_background&lt;&#x2F;span&gt;&lt;span&gt;(Rgb24::new(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;63&lt;&#x2F;span&gt;&lt;span&gt;)),
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Levels now contain stairs!&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;roguelike-tutorial-2020-part-11&#x2F;stairs.png&quot; alt=&quot;stairs.png&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Reference implementation branch: &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;gridbugs&#x2F;chargrid-roguelike-tutorial-2020&#x2F;tree&#x2F;part-11.0&quot;&gt;part-11.0&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;h2 id=&quot;descending-stairs&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#descending-stairs&quot; aria-label=&quot;Anchor link for: descending-stairs&quot;&gt;Descending Stairs&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;To approach the problem of descending stairs top down, start by adding a new control such that
when the ‘&amp;gt;’ key is pressed, attempt to have the player descend to the next dungeon level.
Of course this will not do anything unless the player is currently standing on the stairs.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; app.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;impl &lt;&#x2F;span&gt;&lt;span&gt;AppData {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;handle_input&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut &lt;&#x2F;span&gt;&lt;span&gt;self, input: Input) -&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Option&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;GameReturn&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;match&lt;&#x2F;span&gt;&lt;span&gt; input {
&lt;&#x2F;span&gt;&lt;span&gt;            Input::Keyboard(key) &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;                &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;match&lt;&#x2F;span&gt;&lt;span&gt; key {
&lt;&#x2F;span&gt;&lt;span&gt;                    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;                    KeyboardInput::Char(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;#39;&amp;gt;&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;) &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span&gt;self.game_state.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;maybe_player_descend&lt;&#x2F;span&gt;&lt;span&gt;(),
&lt;&#x2F;span&gt;&lt;span&gt;                    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;                }
&lt;&#x2F;span&gt;&lt;span&gt;                &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;            }
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;        }
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The &lt;code&gt;maybe_player_descend&lt;&#x2F;code&gt; method will check whether the player is on the stairs, and call &lt;code&gt;player_descend&lt;&#x2F;code&gt; if they are.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; game.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;impl &lt;&#x2F;span&gt;&lt;span&gt;GameState {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;pub fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;maybe_player_descend&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut &lt;&#x2F;span&gt;&lt;span&gt;self) {
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;if &lt;&#x2F;span&gt;&lt;span&gt;self.world.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;coord_contains_stairs&lt;&#x2F;span&gt;&lt;span&gt;(self.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;player_coord&lt;&#x2F;span&gt;&lt;span&gt;()) {
&lt;&#x2F;span&gt;&lt;span&gt;            self.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;player_descend&lt;&#x2F;span&gt;&lt;span&gt;();
&lt;&#x2F;span&gt;&lt;span&gt;        }
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;code&gt;player_descend&lt;&#x2F;code&gt; contains the interesting logic relating to descending the stairs.
Comments inline.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; game.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;impl &lt;&#x2F;span&gt;&lt;span&gt;GameState {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;player_descend&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut &lt;&#x2F;span&gt;&lt;span&gt;self) {
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; remove and return the player&amp;#39;s data
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; player_data &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span&gt;self.world.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;remove_character&lt;&#x2F;span&gt;&lt;span&gt;(self.player_entity);
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; remove and discard all entities from the world
&lt;&#x2F;span&gt;&lt;span&gt;        self.world.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;clear&lt;&#x2F;span&gt;&lt;span&gt;();
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; forget the visible areas of the map
&lt;&#x2F;span&gt;&lt;span&gt;        self.visibility_grid.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;clear&lt;&#x2F;span&gt;&lt;span&gt;();
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; generate a fresh level
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; Populate {
&lt;&#x2F;span&gt;&lt;span&gt;            player_entity,
&lt;&#x2F;span&gt;&lt;span&gt;            ai_state,
&lt;&#x2F;span&gt;&lt;span&gt;        } &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span&gt;self.world.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;populate&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut &lt;&#x2F;span&gt;&lt;span&gt;self.rng);
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; insert the old player data into the new level
&lt;&#x2F;span&gt;&lt;span&gt;        self.world.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;replace_character&lt;&#x2F;span&gt;&lt;span&gt;(player_entity, player_data);
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; the player&amp;#39;s entity may have changed
&lt;&#x2F;span&gt;&lt;span&gt;        self.player_entity &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; player_entity;
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; replace ai state to match the new level
&lt;&#x2F;span&gt;&lt;span&gt;        self.ai_state &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; ai_state;
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This code depends on several not-yet-implemented methods of &lt;code&gt;World&lt;&#x2F;code&gt; and &lt;code&gt;VisibilityGrid&lt;&#x2F;code&gt;. Let’s implement them now.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; world.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;pub use &lt;&#x2F;span&gt;&lt;span&gt;components::EntityData;
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;pub struct &lt;&#x2F;span&gt;&lt;span&gt;CharacterData {
&lt;&#x2F;span&gt;&lt;span&gt;    entity_data: EntityData,
&lt;&#x2F;span&gt;&lt;span&gt;    inventory_entity_data: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Vec&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Option&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;EntityData&amp;gt;&amp;gt;,
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;impl &lt;&#x2F;span&gt;&lt;span&gt;World {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;pub fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;clear&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut &lt;&#x2F;span&gt;&lt;span&gt;self) {
&lt;&#x2F;span&gt;&lt;span&gt;        self.entity_allocator.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;clear&lt;&#x2F;span&gt;&lt;span&gt;();
&lt;&#x2F;span&gt;&lt;span&gt;        self.components.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;clear&lt;&#x2F;span&gt;&lt;span&gt;();
&lt;&#x2F;span&gt;&lt;span&gt;        self.spatial_table.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;clear&lt;&#x2F;span&gt;&lt;span&gt;();
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;remove_entity_data&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut &lt;&#x2F;span&gt;&lt;span&gt;self, entity: Entity) -&amp;gt; EntityData {
&lt;&#x2F;span&gt;&lt;span&gt;        self.entity_allocator.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;free&lt;&#x2F;span&gt;&lt;span&gt;(entity);
&lt;&#x2F;span&gt;&lt;span&gt;        self.spatial_table.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;remove&lt;&#x2F;span&gt;&lt;span&gt;(entity);
&lt;&#x2F;span&gt;&lt;span&gt;        self.components.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;remove_entity_data&lt;&#x2F;span&gt;&lt;span&gt;(entity)
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;pub fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;remove_character&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut &lt;&#x2F;span&gt;&lt;span&gt;self, entity: Entity) -&amp;gt; CharacterData {
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;let mut&lt;&#x2F;span&gt;&lt;span&gt; entity_data &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span&gt;self.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;remove_entity_data&lt;&#x2F;span&gt;&lt;span&gt;(entity);
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; Remove the inventory from the character. An inventory contains entities referring data
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; in the current world. These data will also be removed here, and combined with the
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; `EntityData` of the character to form a `CharacterData`. When the `CharacterData` is
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; re-inserted into the world, the inventory item data will be inserted first, at which
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; point each item will be assigned a fresh entity. The character will get a brand new
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; inventory containing the new entities.
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; inventory_entity_data &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; entity_data
&lt;&#x2F;span&gt;&lt;span&gt;            .inventory
&lt;&#x2F;span&gt;&lt;span&gt;            .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;take&lt;&#x2F;span&gt;&lt;span&gt;()
&lt;&#x2F;span&gt;&lt;span&gt;            .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;expect&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;character missing inventory&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)
&lt;&#x2F;span&gt;&lt;span&gt;            .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;slots&lt;&#x2F;span&gt;&lt;span&gt;()
&lt;&#x2F;span&gt;&lt;span&gt;            .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;iter&lt;&#x2F;span&gt;&lt;span&gt;()
&lt;&#x2F;span&gt;&lt;span&gt;            .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;map&lt;&#x2F;span&gt;&lt;span&gt;(|maybe_slot| maybe_slot.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;map&lt;&#x2F;span&gt;&lt;span&gt;(|entity| self.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;remove_entity_data&lt;&#x2F;span&gt;&lt;span&gt;(entity)))
&lt;&#x2F;span&gt;&lt;span&gt;            .collect::&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Vec&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;_&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&amp;gt;();
&lt;&#x2F;span&gt;&lt;span&gt;        CharacterData {
&lt;&#x2F;span&gt;&lt;span&gt;            entity_data,
&lt;&#x2F;span&gt;&lt;span&gt;            inventory_entity_data,
&lt;&#x2F;span&gt;&lt;span&gt;        }
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;pub fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;replace_character&lt;&#x2F;span&gt;&lt;span&gt;(
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut &lt;&#x2F;span&gt;&lt;span&gt;self,
&lt;&#x2F;span&gt;&lt;span&gt;        entity: Entity,
&lt;&#x2F;span&gt;&lt;span&gt;        CharacterData {
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;mut&lt;&#x2F;span&gt;&lt;span&gt; entity_data,
&lt;&#x2F;span&gt;&lt;span&gt;            inventory_entity_data,
&lt;&#x2F;span&gt;&lt;span&gt;        }: CharacterData,
&lt;&#x2F;span&gt;&lt;span&gt;    ) {
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; Before inserting the character&amp;#39;s data, create new entities to contain each item in the
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; character&amp;#39;s inventory.
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; inventory_slots &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; inventory_entity_data
&lt;&#x2F;span&gt;&lt;span&gt;            .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;into_iter&lt;&#x2F;span&gt;&lt;span&gt;()
&lt;&#x2F;span&gt;&lt;span&gt;            .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;map&lt;&#x2F;span&gt;&lt;span&gt;(|maybe_entity_data| {
&lt;&#x2F;span&gt;&lt;span&gt;                maybe_entity_data.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;map&lt;&#x2F;span&gt;&lt;span&gt;(|entity_data| {
&lt;&#x2F;span&gt;&lt;span&gt;                    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; entity &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span&gt;self.entity_allocator.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;alloc&lt;&#x2F;span&gt;&lt;span&gt;();
&lt;&#x2F;span&gt;&lt;span&gt;                    self.components.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;update_entity_data&lt;&#x2F;span&gt;&lt;span&gt;(entity, entity_data);
&lt;&#x2F;span&gt;&lt;span&gt;                    entity
&lt;&#x2F;span&gt;&lt;span&gt;                })
&lt;&#x2F;span&gt;&lt;span&gt;            })
&lt;&#x2F;span&gt;&lt;span&gt;            .collect::&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Vec&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;_&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&amp;gt;();
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; Make a new inventory containing the newly created entities, and add it to the character.
&lt;&#x2F;span&gt;&lt;span&gt;        entity_data.inventory &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Some&lt;&#x2F;span&gt;&lt;span&gt;(Inventory {
&lt;&#x2F;span&gt;&lt;span&gt;            slots: inventory_slots,
&lt;&#x2F;span&gt;&lt;span&gt;        });
&lt;&#x2F;span&gt;&lt;span&gt;        self.components.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;update_entity_data&lt;&#x2F;span&gt;&lt;span&gt;(entity, entity_data);
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;pub fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;coord_contains_stairs&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;self, coord: Coord) -&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;bool &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;        self.spatial_table
&lt;&#x2F;span&gt;&lt;span&gt;            .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;layers_at_checked&lt;&#x2F;span&gt;&lt;span&gt;(coord)
&lt;&#x2F;span&gt;&lt;span&gt;            .floor
&lt;&#x2F;span&gt;&lt;span&gt;            .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;map&lt;&#x2F;span&gt;&lt;span&gt;(|floor_entity| self.components.stairs.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;contains&lt;&#x2F;span&gt;&lt;span&gt;(floor_entity))
&lt;&#x2F;span&gt;&lt;span&gt;            .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;unwrap_or&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;false&lt;&#x2F;span&gt;&lt;span&gt;)
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Most of the complexity above is because the items in the player’s inventory need to transition between
dungeon levels along with the player.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; visibility.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;impl &lt;&#x2F;span&gt;&lt;span&gt;VisibilityGrid {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;pub fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;clear&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut &lt;&#x2F;span&gt;&lt;span&gt;self) {
&lt;&#x2F;span&gt;&lt;span&gt;        self.count &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;;
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;for&lt;&#x2F;span&gt;&lt;span&gt; cell &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;in &lt;&#x2F;span&gt;&lt;span&gt;self.grid.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;iter_mut&lt;&#x2F;span&gt;&lt;span&gt;() {
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;*&lt;&#x2F;span&gt;&lt;span&gt;cell &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Default&lt;&#x2F;span&gt;&lt;span&gt;::default();
&lt;&#x2F;span&gt;&lt;span&gt;        }
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;You can now move onto a staircase, and press ‘&amp;gt;’, and you’ll find yourself in a brand-new level.&lt;&#x2F;p&gt;
&lt;p&gt;Reference implementation branch: &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;gridbugs&#x2F;chargrid-roguelike-tutorial-2020&#x2F;tree&#x2F;part-11.1&quot;&gt;part-11.1&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;h2 id=&quot;add-combat-stats&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#add-combat-stats&quot; aria-label=&quot;Anchor link for: add-combat-stats&quot;&gt;Add Combat Stats&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;Soon we’ll allow the player to level up when descending the stairs, but first we need to
make our combat system more complex, so that the player can be improved on several different axes.
We’ll add 4 combat stats to the player:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;base damage: the minimum amount of damage dealt in a melee attack&lt;&#x2F;li&gt;
&lt;li&gt;strength: a random number between 0 and strength is added to the damage dealth in a melee attack&lt;&#x2F;li&gt;
&lt;li&gt;dexterity: if the character would receive melee damage, it is reduced by a random number between 0 and dexterity&lt;&#x2F;li&gt;
&lt;li&gt;intelligence: determines the effectiveness of spells&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;NPCs can only deal melee damage, so they don’t have an intelligence stat, but they still get the other three.&lt;&#x2F;p&gt;
&lt;p&gt;Base damage is an innate property of the character, so it doesn’t increase when you level up.
Of the other 3 stats, the player may choose one to increment when descending the stairs.
Alternatively, we’ll let the player choose to increase their max health by 5.&lt;&#x2F;p&gt;
&lt;p&gt;In this section we’ll add components for each of the stats, and update the UI to display the character’s stats.
In the next section we’ll update the combat system to make use of these stats.&lt;&#x2F;p&gt;
&lt;p&gt;Add components for the stats, add the stats components to the player and NPCs, and expose getters for strength, dexterity and intelligence.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; world.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;entity_table::declare_entity_module&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;! &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;    components {
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;        base_damage: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;i32&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;        strength: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;i32&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;        dexterity: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;i32&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;        intelligence: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;i32&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;impl &lt;&#x2F;span&gt;&lt;span&gt;World {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;spawn_player&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut &lt;&#x2F;span&gt;&lt;span&gt;self, coord: Coord) -&amp;gt; Entity {
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;        self.components.base_damage.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;insert&lt;&#x2F;span&gt;&lt;span&gt;(entity, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;);
&lt;&#x2F;span&gt;&lt;span&gt;        self.components.strength.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;insert&lt;&#x2F;span&gt;&lt;span&gt;(entity, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;);
&lt;&#x2F;span&gt;&lt;span&gt;        self.components.dexterity.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;insert&lt;&#x2F;span&gt;&lt;span&gt;(entity, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;);
&lt;&#x2F;span&gt;&lt;span&gt;        self.components.intelligence.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;insert&lt;&#x2F;span&gt;&lt;span&gt;(entity, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;);
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;spawn_npc&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut &lt;&#x2F;span&gt;&lt;span&gt;self, coord: Coord, npc_type: NpcType) -&amp;gt; Entity {
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;        self.components.base_damage.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;insert&lt;&#x2F;span&gt;&lt;span&gt;(entity, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;);
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;let &lt;&#x2F;span&gt;&lt;span&gt;(strength, dexterity) &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= match&lt;&#x2F;span&gt;&lt;span&gt; npc_type {
&lt;&#x2F;span&gt;&lt;span&gt;            NpcType::Orc &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;),
&lt;&#x2F;span&gt;&lt;span&gt;            NpcType::Troll &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;2&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt;),
&lt;&#x2F;span&gt;&lt;span&gt;        };
&lt;&#x2F;span&gt;&lt;span&gt;        self.components.strength.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;insert&lt;&#x2F;span&gt;&lt;span&gt;(entity, strength);
&lt;&#x2F;span&gt;&lt;span&gt;        self.components.dexterity.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;insert&lt;&#x2F;span&gt;&lt;span&gt;(entity, dexterity);
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;pub fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;strength&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;self, entity: Entity) -&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Option&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;i32&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;        self.components.strength.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;get&lt;&#x2F;span&gt;&lt;span&gt;(entity).&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;cloned&lt;&#x2F;span&gt;&lt;span&gt;()
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;pub fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;dexterity&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;self, entity: Entity) -&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Option&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;i32&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;        self.components.dexterity.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;get&lt;&#x2F;span&gt;&lt;span&gt;(entity).&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;cloned&lt;&#x2F;span&gt;&lt;span&gt;()
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;pub fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;intelligence&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;self, entity: Entity) -&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Option&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;i32&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;        self.components.intelligence.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;get&lt;&#x2F;span&gt;&lt;span&gt;(entity).&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;cloned&lt;&#x2F;span&gt;&lt;span&gt;()
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Add some getters for the player’s stats to &lt;code&gt;GameState&lt;&#x2F;code&gt;, and also keep track of the current dungeon level.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; game.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;pub struct &lt;&#x2F;span&gt;&lt;span&gt;GameState {
&lt;&#x2F;span&gt;&lt;span&gt;    ...
&lt;&#x2F;span&gt;&lt;span&gt;    dungeon_level: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;u32&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;impl &lt;&#x2F;span&gt;&lt;span&gt;GameState {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;pub fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;new&lt;&#x2F;span&gt;&lt;span&gt;(
&lt;&#x2F;span&gt;&lt;span&gt;        screen_size: Size,
&lt;&#x2F;span&gt;&lt;span&gt;        rng_seed: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;u64&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;        initial_visibility_algorithm: VisibilityAlgorithm,
&lt;&#x2F;span&gt;&lt;span&gt;    ) -&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;Self &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; dungeon_level &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;;
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;let mut&lt;&#x2F;span&gt;&lt;span&gt; game_state &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= Self &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;            dungeon_level,
&lt;&#x2F;span&gt;&lt;span&gt;        };
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;player_descend&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut &lt;&#x2F;span&gt;&lt;span&gt;self) {
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;        self.dungeon_level &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;+= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;;
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;pub fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;player_strength&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;self) -&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;i32 &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;        self.world
&lt;&#x2F;span&gt;&lt;span&gt;            .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;strength&lt;&#x2F;span&gt;&lt;span&gt;(self.player_entity)
&lt;&#x2F;span&gt;&lt;span&gt;            .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;expect&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;player missing strength&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;pub fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;player_dexterity&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;self) -&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;i32 &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;        self.world
&lt;&#x2F;span&gt;&lt;span&gt;            .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;dexterity&lt;&#x2F;span&gt;&lt;span&gt;(self.player_entity)
&lt;&#x2F;span&gt;&lt;span&gt;            .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;expect&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;player missing dexterity&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;pub fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;player_intelligence&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;self) -&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;i32 &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;        self.world
&lt;&#x2F;span&gt;&lt;span&gt;            .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;intelligence&lt;&#x2F;span&gt;&lt;span&gt;(self.player_entity)
&lt;&#x2F;span&gt;&lt;span&gt;            .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;expect&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;player missing intelligence&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;pub fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;dungeon_level&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;self) -&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;u32 &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;        self.dungeon_level
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;In &lt;code&gt;app.rs&lt;&#x2F;code&gt;, pass the player’s stats to the ui renderer. Also reduce the padding around
the ui to make space for the extra information.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; app.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;use crate&lt;&#x2F;span&gt;&lt;span&gt;::ui::{StatsData, UiData, UiView};
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;impl &lt;&#x2F;span&gt;&lt;span&gt;AppView {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;new&lt;&#x2F;span&gt;&lt;span&gt;(screen_size: Size) -&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;Self &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;const &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;UI_Y_PADDING&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;u32 = &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt;;
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;render_ui&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;F: Frame, C: ColModify&amp;gt;(
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut &lt;&#x2F;span&gt;&lt;span&gt;self,
&lt;&#x2F;span&gt;&lt;span&gt;        name: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Option&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&amp;#39;static str&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;,
&lt;&#x2F;span&gt;&lt;span&gt;        data: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;AppData,
&lt;&#x2F;span&gt;&lt;span&gt;        context: ViewContext&amp;lt;C&amp;gt;,
&lt;&#x2F;span&gt;&lt;span&gt;        frame: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut&lt;&#x2F;span&gt;&lt;span&gt; F,
&lt;&#x2F;span&gt;&lt;span&gt;    ) {
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;        self.ui_view.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;view&lt;&#x2F;span&gt;&lt;span&gt;(
&lt;&#x2F;span&gt;&lt;span&gt;            UiData {
&lt;&#x2F;span&gt;&lt;span&gt;                player_hit_points,
&lt;&#x2F;span&gt;&lt;span&gt;                messages,
&lt;&#x2F;span&gt;&lt;span&gt;                name,
&lt;&#x2F;span&gt;&lt;span&gt;                examine_cell,
&lt;&#x2F;span&gt;&lt;span&gt;                stats_data: StatsData {
&lt;&#x2F;span&gt;&lt;span&gt;                    strength: data.game_state.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;player_strength&lt;&#x2F;span&gt;&lt;span&gt;(),
&lt;&#x2F;span&gt;&lt;span&gt;                    dexterity: data.game_state.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;player_dexterity&lt;&#x2F;span&gt;&lt;span&gt;(),
&lt;&#x2F;span&gt;&lt;span&gt;                    intelligence: data.game_state.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;player_intelligence&lt;&#x2F;span&gt;&lt;span&gt;(),
&lt;&#x2F;span&gt;&lt;span&gt;                },
&lt;&#x2F;span&gt;&lt;span&gt;                dungeon_level: data.game_state.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;dungeon_level&lt;&#x2F;span&gt;&lt;span&gt;(),
&lt;&#x2F;span&gt;&lt;span&gt;            },
&lt;&#x2F;span&gt;&lt;span&gt;            context.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;add_offset&lt;&#x2F;span&gt;&lt;span&gt;(Coord::new(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt;, self.ui_y_offset)),
&lt;&#x2F;span&gt;&lt;span&gt;            frame,
&lt;&#x2F;span&gt;&lt;span&gt;        );
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Add a ui component for showing the player’s stats.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; ui.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;#[derive(Default)]
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;struct &lt;&#x2F;span&gt;&lt;span&gt;StatsView {
&lt;&#x2F;span&gt;&lt;span&gt;    buf: String,
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;pub struct &lt;&#x2F;span&gt;&lt;span&gt;StatsData {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;pub &lt;&#x2F;span&gt;&lt;span&gt;strength: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;i32&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;pub &lt;&#x2F;span&gt;&lt;span&gt;dexterity: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;i32&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;pub &lt;&#x2F;span&gt;&lt;span&gt;intelligence: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;i32&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;impl&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;#39;a&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt; View&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&amp;#39;a&lt;&#x2F;span&gt;&lt;span&gt; StatsData&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;for &lt;&#x2F;span&gt;&lt;span&gt;StatsView {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;view&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;F: Frame, C: ColModify&amp;gt;(
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut &lt;&#x2F;span&gt;&lt;span&gt;self,
&lt;&#x2F;span&gt;&lt;span&gt;        data: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&amp;#39;a&lt;&#x2F;span&gt;&lt;span&gt; StatsData,
&lt;&#x2F;span&gt;&lt;span&gt;        context: ViewContext&amp;lt;C&amp;gt;,
&lt;&#x2F;span&gt;&lt;span&gt;        frame: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut&lt;&#x2F;span&gt;&lt;span&gt; F,
&lt;&#x2F;span&gt;&lt;span&gt;    ) {
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;use &lt;&#x2F;span&gt;&lt;span&gt;std::fmt::Write;
&lt;&#x2F;span&gt;&lt;span&gt;        self.buf.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;clear&lt;&#x2F;span&gt;&lt;span&gt;();
&lt;&#x2F;span&gt;&lt;span&gt;        write!(
&lt;&#x2F;span&gt;&lt;span&gt;            &amp;amp;mut self.buf,
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;str: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;{}&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;, dex: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;{}&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;, int: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;{}&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;            data.strength, data.dexterity, data.intelligence
&lt;&#x2F;span&gt;&lt;span&gt;        )
&lt;&#x2F;span&gt;&lt;span&gt;        .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;unwrap&lt;&#x2F;span&gt;&lt;span&gt;();
&lt;&#x2F;span&gt;&lt;span&gt;        StringViewSingleLine::new(Style::new().&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;with_foreground&lt;&#x2F;span&gt;&lt;span&gt;(Rgb24::new_grey(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;187&lt;&#x2F;span&gt;&lt;span&gt;)))
&lt;&#x2F;span&gt;&lt;span&gt;            .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;view&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;self.buf, context, frame);
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;And another component for the current dungeon level.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; ui.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;#[derive(Default)]
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;struct &lt;&#x2F;span&gt;&lt;span&gt;DungeonLevelView {
&lt;&#x2F;span&gt;&lt;span&gt;    buf: String,
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;impl &lt;&#x2F;span&gt;&lt;span&gt;View&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;u32&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;for &lt;&#x2F;span&gt;&lt;span&gt;DungeonLevelView {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;view&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;F: Frame, C: ColModify&amp;gt;(
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut &lt;&#x2F;span&gt;&lt;span&gt;self,
&lt;&#x2F;span&gt;&lt;span&gt;        dungeon_level: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;u32&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;        context: ViewContext&amp;lt;C&amp;gt;,
&lt;&#x2F;span&gt;&lt;span&gt;        frame: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut&lt;&#x2F;span&gt;&lt;span&gt; F,
&lt;&#x2F;span&gt;&lt;span&gt;    ) {
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;use &lt;&#x2F;span&gt;&lt;span&gt;std::fmt::Write;
&lt;&#x2F;span&gt;&lt;span&gt;        self.buf.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;clear&lt;&#x2F;span&gt;&lt;span&gt;();
&lt;&#x2F;span&gt;&lt;span&gt;        write!(&amp;amp;mut self.buf, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;Level: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;{}&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;, dungeon_level).&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;unwrap&lt;&#x2F;span&gt;&lt;span&gt;();
&lt;&#x2F;span&gt;&lt;span&gt;        StringViewSingleLine::new(Style::new().&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;with_foreground&lt;&#x2F;span&gt;&lt;span&gt;(Rgb24::new_grey(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;187&lt;&#x2F;span&gt;&lt;span&gt;)))
&lt;&#x2F;span&gt;&lt;span&gt;            .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;view&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;self.buf, context, frame);
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Add fields for stats and dungeon level to &lt;code&gt;UiData&lt;&#x2F;code&gt; and &lt;code&gt;UiView&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; ui.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;pub struct &lt;&#x2F;span&gt;&lt;span&gt;UiData&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;#39;a&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;    ...
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;pub &lt;&#x2F;span&gt;&lt;span&gt;stats_data: StatsData,
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;pub &lt;&#x2F;span&gt;&lt;span&gt;dungeon_level: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;u32&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;#[derive(Default)]
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;pub struct &lt;&#x2F;span&gt;&lt;span&gt;UiView {
&lt;&#x2F;span&gt;&lt;span&gt;    ...
&lt;&#x2F;span&gt;&lt;span&gt;    stats_view: StatsView,
&lt;&#x2F;span&gt;&lt;span&gt;    dungeon_level_view: DungeonLevelView,
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Finally update &lt;code&gt;UiView::view&lt;&#x2F;code&gt; to draw the new components.
The whole function is shown here as it’s changed significantly.
Also note the new helper function &lt;code&gt;centre_health_width&lt;&#x2F;code&gt; for centering
text within the width of the health bar.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; ui.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;centre_health_width&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;T: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Clone&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;(view: impl View&amp;lt;T&amp;gt;, height: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;u32&lt;&#x2F;span&gt;&lt;span&gt;) -&amp;gt; impl View&amp;lt;T&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;    BoundView {
&lt;&#x2F;span&gt;&lt;span&gt;        size: Size::new(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;HEALTH_WIDTH&lt;&#x2F;span&gt;&lt;span&gt;, height),
&lt;&#x2F;span&gt;&lt;span&gt;        view: AlignView {
&lt;&#x2F;span&gt;&lt;span&gt;            alignment: Alignment {
&lt;&#x2F;span&gt;&lt;span&gt;                x: AlignmentX::Centre,
&lt;&#x2F;span&gt;&lt;span&gt;                y: AlignmentY::Bottom,
&lt;&#x2F;span&gt;&lt;span&gt;            },
&lt;&#x2F;span&gt;&lt;span&gt;            view,
&lt;&#x2F;span&gt;&lt;span&gt;        },
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;impl&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;#39;a&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt; View&amp;lt;UiData&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;#39;a&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;for &lt;&#x2F;span&gt;&lt;span&gt;UiView {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;view&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;F: Frame, C: ColModify&amp;gt;(
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut &lt;&#x2F;span&gt;&lt;span&gt;self,
&lt;&#x2F;span&gt;&lt;span&gt;        data: UiData,
&lt;&#x2F;span&gt;&lt;span&gt;        context: ViewContext&amp;lt;C&amp;gt;,
&lt;&#x2F;span&gt;&lt;span&gt;        frame: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut&lt;&#x2F;span&gt;&lt;span&gt; F,
&lt;&#x2F;span&gt;&lt;span&gt;    ) {
&lt;&#x2F;span&gt;&lt;span&gt;        self.health_view
&lt;&#x2F;span&gt;&lt;span&gt;            .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;view&lt;&#x2F;span&gt;&lt;span&gt;(data.player_hit_points, context, frame);
&lt;&#x2F;span&gt;&lt;span&gt;        self.stats_view.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;view&lt;&#x2F;span&gt;&lt;span&gt;(
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;data.stats_data,
&lt;&#x2F;span&gt;&lt;span&gt;            context.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;add_offset&lt;&#x2F;span&gt;&lt;span&gt;(Coord::new(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;HEALTH_WIDTH &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;as i32 + &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt;)),
&lt;&#x2F;span&gt;&lt;span&gt;            frame,
&lt;&#x2F;span&gt;&lt;span&gt;        );
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;centre_health_width&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut &lt;&#x2F;span&gt;&lt;span&gt;self.dungeon_level_view, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;).&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;view&lt;&#x2F;span&gt;&lt;span&gt;(
&lt;&#x2F;span&gt;&lt;span&gt;            data.dungeon_level,
&lt;&#x2F;span&gt;&lt;span&gt;            context.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;add_offset&lt;&#x2F;span&gt;&lt;span&gt;(Coord::new(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;)),
&lt;&#x2F;span&gt;&lt;span&gt;            frame,
&lt;&#x2F;span&gt;&lt;span&gt;        );
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; message_log_offset &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span&gt;Coord::new(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;HEALTH_WIDTH &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;as i32 + &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;);
&lt;&#x2F;span&gt;&lt;span&gt;        self.messages_view
&lt;&#x2F;span&gt;&lt;span&gt;            .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;view&lt;&#x2F;span&gt;&lt;span&gt;(data.messages, context.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;add_offset&lt;&#x2F;span&gt;&lt;span&gt;(message_log_offset), frame);
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;if let &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Some&lt;&#x2F;span&gt;&lt;span&gt;(name) &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; data.name {
&lt;&#x2F;span&gt;&lt;span&gt;            BoundView {
&lt;&#x2F;span&gt;&lt;span&gt;                size: Size::new(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;HEALTH_WIDTH&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;),
&lt;&#x2F;span&gt;&lt;span&gt;                view: AlignView {
&lt;&#x2F;span&gt;&lt;span&gt;                    alignment: Alignment::centre(),
&lt;&#x2F;span&gt;&lt;span&gt;                    view: StringViewSingleLine::new(
&lt;&#x2F;span&gt;&lt;span&gt;                        Style::new().&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;with_foreground&lt;&#x2F;span&gt;&lt;span&gt;(Rgb24::new_grey(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;255&lt;&#x2F;span&gt;&lt;span&gt;)),
&lt;&#x2F;span&gt;&lt;span&gt;                    ),
&lt;&#x2F;span&gt;&lt;span&gt;                },
&lt;&#x2F;span&gt;&lt;span&gt;            }
&lt;&#x2F;span&gt;&lt;span&gt;            .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;view&lt;&#x2F;span&gt;&lt;span&gt;(name, context.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;add_offset&lt;&#x2F;span&gt;&lt;span&gt;(Coord::new(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;2&lt;&#x2F;span&gt;&lt;span&gt;)), frame);
&lt;&#x2F;span&gt;&lt;span&gt;        }
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;if let &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Some&lt;&#x2F;span&gt;&lt;span&gt;(examine_cell) &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; data.examine_cell {
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;centre_health_width&lt;&#x2F;span&gt;&lt;span&gt;(
&lt;&#x2F;span&gt;&lt;span&gt;                StringView::new(
&lt;&#x2F;span&gt;&lt;span&gt;                    Style::new().&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;with_foreground&lt;&#x2F;span&gt;&lt;span&gt;(Rgb24::new_grey(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;187&lt;&#x2F;span&gt;&lt;span&gt;)),
&lt;&#x2F;span&gt;&lt;span&gt;                    wrap::Word::new(),
&lt;&#x2F;span&gt;&lt;span&gt;                ),
&lt;&#x2F;span&gt;&lt;span&gt;                &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;2&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;            )
&lt;&#x2F;span&gt;&lt;span&gt;            .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;view&lt;&#x2F;span&gt;&lt;span&gt;(
&lt;&#x2F;span&gt;&lt;span&gt;                &lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;examine_cell_str&lt;&#x2F;span&gt;&lt;span&gt;(examine_cell),
&lt;&#x2F;span&gt;&lt;span&gt;                context.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;add_offset&lt;&#x2F;span&gt;&lt;span&gt;(Coord::new(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;3&lt;&#x2F;span&gt;&lt;span&gt;)),
&lt;&#x2F;span&gt;&lt;span&gt;                frame,
&lt;&#x2F;span&gt;&lt;span&gt;            );
&lt;&#x2F;span&gt;&lt;span&gt;        }
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Running the game, the ui changes should be visible. The level counter will increase
as you descend deeper into the dungeon.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;roguelike-tutorial-2020-part-11&#x2F;stats.png&quot; alt=&quot;stats.png&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Reference implementation branch: &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;gridbugs&#x2F;chargrid-roguelike-tutorial-2020&#x2F;tree&#x2F;part-11.2&quot;&gt;part-11.2&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;h2 id=&quot;use-combat-stats&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#use-combat-stats&quot; aria-label=&quot;Anchor link for: use-combat-stats&quot;&gt;Use Combat Stats&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;We’ll start by replacing the logic for handling bump attacks to take the new stats into account.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; world.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;enum &lt;&#x2F;span&gt;&lt;span&gt;BumpAttackOutcome {
&lt;&#x2F;span&gt;&lt;span&gt;    Hit,
&lt;&#x2F;span&gt;&lt;span&gt;    Dodge,
&lt;&#x2F;span&gt;&lt;span&gt;    Kill,
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;impl &lt;&#x2F;span&gt;&lt;span&gt;World {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;character_bump_attack&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;R: Rng&amp;gt;(
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut &lt;&#x2F;span&gt;&lt;span&gt;self,
&lt;&#x2F;span&gt;&lt;span&gt;        victim: Entity,
&lt;&#x2F;span&gt;&lt;span&gt;        attacker: Entity,
&lt;&#x2F;span&gt;&lt;span&gt;        rng: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut&lt;&#x2F;span&gt;&lt;span&gt; R,
&lt;&#x2F;span&gt;&lt;span&gt;    ) -&amp;gt; BumpAttackOutcome {
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;let &amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;attacker_base_damage &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span&gt;self.components.base_damage.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;get&lt;&#x2F;span&gt;&lt;span&gt;(attacker).&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;unwrap&lt;&#x2F;span&gt;&lt;span&gt;();
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;let &amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;attacker_strength &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span&gt;self.components.strength.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;get&lt;&#x2F;span&gt;&lt;span&gt;(attacker).&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;unwrap&lt;&#x2F;span&gt;&lt;span&gt;();
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;let &amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;victim_dexterity &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span&gt;self.components.dexterity.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;get&lt;&#x2F;span&gt;&lt;span&gt;(victim).&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;unwrap&lt;&#x2F;span&gt;&lt;span&gt;();
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; gross_damage &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; attacker_base_damage &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;+&lt;&#x2F;span&gt;&lt;span&gt; rng.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;gen_range&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;..&lt;&#x2F;span&gt;&lt;span&gt;(attacker_strength &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;+ &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;));
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; damage_reduction &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; rng.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;gen_range&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;..&lt;&#x2F;span&gt;&lt;span&gt;(victim_dexterity &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;+ &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;));
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; net_damage &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; gross_damage.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;saturating_sub&lt;&#x2F;span&gt;&lt;span&gt;(damage_reduction) &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;as u32&lt;&#x2F;span&gt;&lt;span&gt;;
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;if&lt;&#x2F;span&gt;&lt;span&gt; net_damage &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;== &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;0 &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;            BumpAttackOutcome::Dodge
&lt;&#x2F;span&gt;&lt;span&gt;        } &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;else &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;if &lt;&#x2F;span&gt;&lt;span&gt;self.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;character_damage&lt;&#x2F;span&gt;&lt;span&gt;(victim, net_damage).&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;is_some&lt;&#x2F;span&gt;&lt;span&gt;() {
&lt;&#x2F;span&gt;&lt;span&gt;                BumpAttackOutcome::Kill
&lt;&#x2F;span&gt;&lt;span&gt;            } &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;else &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;                BumpAttackOutcome::Hit
&lt;&#x2F;span&gt;&lt;span&gt;            }
&lt;&#x2F;span&gt;&lt;span&gt;        }
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Note that if an attack would deal 0 damage, we say that it was dodged instead, to add some flavour.
Also note that &lt;code&gt;character_bump_attack&lt;&#x2F;code&gt; now takes an additional argument - a random number generator.
Its return type has also changed.&lt;&#x2F;p&gt;
&lt;p&gt;Update &lt;code&gt;maybe_move_character&lt;&#x2F;code&gt; to account for this function’s new signature.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; world.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;impl &lt;&#x2F;span&gt;&lt;span&gt;World {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;pub fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;maybe_move_character&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;R: Rng&amp;gt;(
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut &lt;&#x2F;span&gt;&lt;span&gt;self,
&lt;&#x2F;span&gt;&lt;span&gt;        character_entity: Entity,
&lt;&#x2F;span&gt;&lt;span&gt;        direction: CardinalDirection,
&lt;&#x2F;span&gt;&lt;span&gt;        message_log: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Vec&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;LogMessage&amp;gt;,
&lt;&#x2F;span&gt;&lt;span&gt;        rng: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut&lt;&#x2F;span&gt;&lt;span&gt; R,
&lt;&#x2F;span&gt;&lt;span&gt;    ) {
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;if&lt;&#x2F;span&gt;&lt;span&gt; new_character_coord.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;is_valid&lt;&#x2F;span&gt;&lt;span&gt;(self.spatial_table.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;grid_size&lt;&#x2F;span&gt;&lt;span&gt;()) {
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; dest_layers &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span&gt;self.spatial_table.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;layers_at_checked&lt;&#x2F;span&gt;&lt;span&gt;(new_character_coord);
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;if let &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Some&lt;&#x2F;span&gt;&lt;span&gt;(dest_character_entity) &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; dest_layers.character {
&lt;&#x2F;span&gt;&lt;span&gt;                &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; character_is_npc &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span&gt;self.components.npc_type.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;get&lt;&#x2F;span&gt;&lt;span&gt;(character_entity).&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;cloned&lt;&#x2F;span&gt;&lt;span&gt;();
&lt;&#x2F;span&gt;&lt;span&gt;                &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; dest_character_is_npc &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=
&lt;&#x2F;span&gt;&lt;span&gt;                    self.components.npc_type.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;get&lt;&#x2F;span&gt;&lt;span&gt;(dest_character_entity).&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;cloned&lt;&#x2F;span&gt;&lt;span&gt;();
&lt;&#x2F;span&gt;&lt;span&gt;                &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;if&lt;&#x2F;span&gt;&lt;span&gt; character_is_npc.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;is_some&lt;&#x2F;span&gt;&lt;span&gt;() &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;!=&lt;&#x2F;span&gt;&lt;span&gt; dest_character_is_npc.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;is_some&lt;&#x2F;span&gt;&lt;span&gt;() {
&lt;&#x2F;span&gt;&lt;span&gt;                    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; outcome &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=
&lt;&#x2F;span&gt;&lt;span&gt;                        self.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;character_bump_attack&lt;&#x2F;span&gt;&lt;span&gt;(dest_character_entity, character_entity, rng);
&lt;&#x2F;span&gt;&lt;span&gt;                    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; npc_type &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; character_is_npc.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;or&lt;&#x2F;span&gt;&lt;span&gt;(dest_character_is_npc).&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;unwrap&lt;&#x2F;span&gt;&lt;span&gt;();
&lt;&#x2F;span&gt;&lt;span&gt;                    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;Self&lt;&#x2F;span&gt;&lt;span&gt;::write_combat_log_messages(
&lt;&#x2F;span&gt;&lt;span&gt;                        character_is_npc.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;is_none&lt;&#x2F;span&gt;&lt;span&gt;(),
&lt;&#x2F;span&gt;&lt;span&gt;                        outcome,
&lt;&#x2F;span&gt;&lt;span&gt;                        npc_type,
&lt;&#x2F;span&gt;&lt;span&gt;                        message_log,
&lt;&#x2F;span&gt;&lt;span&gt;                    );
&lt;&#x2F;span&gt;&lt;span&gt;                }
&lt;&#x2F;span&gt;&lt;span&gt;            } &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;else if&lt;&#x2F;span&gt;&lt;span&gt; dest_layers.feature.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;is_none&lt;&#x2F;span&gt;&lt;span&gt;() {
&lt;&#x2F;span&gt;&lt;span&gt;                self.spatial_table
&lt;&#x2F;span&gt;&lt;span&gt;                    .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;update_coord&lt;&#x2F;span&gt;&lt;span&gt;(character_entity, new_character_coord)
&lt;&#x2F;span&gt;&lt;span&gt;                    .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;unwrap&lt;&#x2F;span&gt;&lt;span&gt;();
&lt;&#x2F;span&gt;&lt;span&gt;            }
&lt;&#x2F;span&gt;&lt;span&gt;        }
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The &lt;code&gt;BumpAttackOutcome&lt;&#x2F;code&gt; is now being passed to &lt;code&gt;write_combat_log_messages&lt;&#x2F;code&gt;, so update that too.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; world.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;impl &lt;&#x2F;span&gt;&lt;span&gt;World {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;write_combat_log_messages&lt;&#x2F;span&gt;&lt;span&gt;(
&lt;&#x2F;span&gt;&lt;span&gt;        attacker_is_player: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;bool&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;        outcome: BumpAttackOutcome,
&lt;&#x2F;span&gt;&lt;span&gt;        npc_type: NpcType,
&lt;&#x2F;span&gt;&lt;span&gt;        message_log: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Vec&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;LogMessage&amp;gt;,
&lt;&#x2F;span&gt;&lt;span&gt;    ) {
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;if&lt;&#x2F;span&gt;&lt;span&gt; attacker_is_player {
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;match&lt;&#x2F;span&gt;&lt;span&gt; outcome {
&lt;&#x2F;span&gt;&lt;span&gt;                BumpAttackOutcome::Kill &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt; message_log.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;push&lt;&#x2F;span&gt;&lt;span&gt;(LogMessage::PlayerKillsNpc(npc_type)),
&lt;&#x2F;span&gt;&lt;span&gt;                BumpAttackOutcome::Hit &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt; message_log.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;push&lt;&#x2F;span&gt;&lt;span&gt;(LogMessage::PlayerAttacksNpc(npc_type)),
&lt;&#x2F;span&gt;&lt;span&gt;                BumpAttackOutcome::Dodge &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt; message_log.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;push&lt;&#x2F;span&gt;&lt;span&gt;(LogMessage::NpcDodges(npc_type)),
&lt;&#x2F;span&gt;&lt;span&gt;            }
&lt;&#x2F;span&gt;&lt;span&gt;        } &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;else &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;match&lt;&#x2F;span&gt;&lt;span&gt; outcome {
&lt;&#x2F;span&gt;&lt;span&gt;                BumpAttackOutcome::Kill &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt; message_log.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;push&lt;&#x2F;span&gt;&lt;span&gt;(LogMessage::NpcKillsPlayer(npc_type)),
&lt;&#x2F;span&gt;&lt;span&gt;                BumpAttackOutcome::Hit &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt; message_log.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;push&lt;&#x2F;span&gt;&lt;span&gt;(LogMessage::NpcAttacksPlayer(npc_type)),
&lt;&#x2F;span&gt;&lt;span&gt;                BumpAttackOutcome::Dodge &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt; message_log.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;push&lt;&#x2F;span&gt;&lt;span&gt;(LogMessage::PlayerDodges(npc_type)),
&lt;&#x2F;span&gt;&lt;span&gt;            }
&lt;&#x2F;span&gt;&lt;span&gt;        }
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This makes use of two new &lt;code&gt;LogMessage&lt;&#x2F;code&gt;s. Define them.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; game.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;pub enum &lt;&#x2F;span&gt;&lt;span&gt;LogMessage {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;    PlayerDodges(NpcType),
&lt;&#x2F;span&gt;&lt;span&gt;    NpcDodges(NpcType),
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;And update the ui code to print out the messages.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; ui.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;impl&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;#39;a&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt; View&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&amp;#39;a &lt;&#x2F;span&gt;&lt;span&gt;[LogMessage]&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;for &lt;&#x2F;span&gt;&lt;span&gt;MessagesView {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;view&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;F: Frame, C: ColModify&amp;gt;(
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut &lt;&#x2F;span&gt;&lt;span&gt;self,
&lt;&#x2F;span&gt;&lt;span&gt;        messages: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&amp;#39;a&lt;&#x2F;span&gt;&lt;span&gt; [LogMessage],
&lt;&#x2F;span&gt;&lt;span&gt;        context: ViewContext&amp;lt;C&amp;gt;,
&lt;&#x2F;span&gt;&lt;span&gt;        frame: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut&lt;&#x2F;span&gt;&lt;span&gt; F,
&lt;&#x2F;span&gt;&lt;span&gt;    ) {
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;format_message&lt;&#x2F;span&gt;&lt;span&gt;(buf: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut&lt;&#x2F;span&gt;&lt;span&gt; [RichTextPartOwned], message: LogMessage) {
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;match&lt;&#x2F;span&gt;&lt;span&gt; message {
&lt;&#x2F;span&gt;&lt;span&gt;                &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;                PlayerDodges(npc_type) &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;                    write!(&amp;amp;mut buf[0].text, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;You dodge the &amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;).&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;unwrap&lt;&#x2F;span&gt;&lt;span&gt;();
&lt;&#x2F;span&gt;&lt;span&gt;                    write!(&amp;amp;mut buf[1].text, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;{}&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;#39;s&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;, npc_type.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;name&lt;&#x2F;span&gt;&lt;span&gt;()).&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;unwrap&lt;&#x2F;span&gt;&lt;span&gt;();
&lt;&#x2F;span&gt;&lt;span&gt;                    buf[&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;].style.foreground &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Some&lt;&#x2F;span&gt;&lt;span&gt;(colours::npc_colour(npc_type));
&lt;&#x2F;span&gt;&lt;span&gt;                    write!(&amp;amp;mut buf[2].text, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot; attack.&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;).&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;unwrap&lt;&#x2F;span&gt;&lt;span&gt;();
&lt;&#x2F;span&gt;&lt;span&gt;                }
&lt;&#x2F;span&gt;&lt;span&gt;                NpcDodges(npc_type) &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;                    write!(&amp;amp;mut buf[0].text, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;The &amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;).&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;unwrap&lt;&#x2F;span&gt;&lt;span&gt;();
&lt;&#x2F;span&gt;&lt;span&gt;                    write!(&amp;amp;mut buf[1].text, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;{}&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;, npc_type.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;name&lt;&#x2F;span&gt;&lt;span&gt;()).&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;unwrap&lt;&#x2F;span&gt;&lt;span&gt;();
&lt;&#x2F;span&gt;&lt;span&gt;                    buf[&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;].style.foreground &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Some&lt;&#x2F;span&gt;&lt;span&gt;(colours::npc_colour(npc_type));
&lt;&#x2F;span&gt;&lt;span&gt;                    write!(&amp;amp;mut buf[2].text, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot; dodges your attack.&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;).&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;unwrap&lt;&#x2F;span&gt;&lt;span&gt;();
&lt;&#x2F;span&gt;&lt;span&gt;                }
&lt;&#x2F;span&gt;&lt;span&gt;            }
&lt;&#x2F;span&gt;&lt;span&gt;        }
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;That’s all that’s required to get melee stats working. All that’s left is intelligence.&lt;&#x2F;p&gt;
&lt;p&gt;There are currently two types of spell: fireball and confusion. The damage dealt by fireball,
and the duration of confusion, will both be determined by the intelligence stat.
The control flow around casting the spells involves giving the player a chance to aim,
then launching a projectile which moves, animated in realtime. If it collides with a character,
it has some effect based on which spell it was. Only at the point of impact does the
efficacy of the spell (determined by the caster’s intelligence) need to be known.
The problem is that right now we don’t store an association between the projectile and the
entity which cast it. To make this easy, add fields to the &lt;code&gt;Fireball&lt;&#x2F;code&gt; and &lt;code&gt;Confusion &lt;&#x2F;code&gt; &lt;code&gt;ProjectileType&lt;&#x2F;code&gt;s
storing the damage and duration respectively.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; world.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;pub enum &lt;&#x2F;span&gt;&lt;span&gt;ProjectileType {
&lt;&#x2F;span&gt;&lt;span&gt;    Fireball { damage: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;u32 &lt;&#x2F;span&gt;&lt;span&gt;},
&lt;&#x2F;span&gt;&lt;span&gt;    Confusion { duration: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;u32 &lt;&#x2F;span&gt;&lt;span&gt;},
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Try to compile the code. Any place we pattern-match on a &lt;code&gt;ProjectileType&lt;&#x2F;code&gt; needs to be updated from&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span&gt;  ProjectileType::Fireball &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; ...
&lt;&#x2F;span&gt;&lt;span&gt;  ProjectileType::Confusion &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; ...
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;to&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span&gt;  ProjectileType::Fireball { &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;.. &lt;&#x2F;span&gt;&lt;span&gt;} &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; ...
&lt;&#x2F;span&gt;&lt;span&gt;  ProjectileType::Confusion { &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;.. &lt;&#x2F;span&gt;&lt;span&gt;} &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; ...
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Update &lt;code&gt;World::maybe_use_item_aim&lt;&#x2F;code&gt; to set these new fields based on the caster’s intelligence.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; world.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;impl &lt;&#x2F;span&gt;&lt;span&gt;World {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;pub fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;maybe_use_item_aim&lt;&#x2F;span&gt;&lt;span&gt;(
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut &lt;&#x2F;span&gt;&lt;span&gt;self,
&lt;&#x2F;span&gt;&lt;span&gt;        character: Entity,
&lt;&#x2F;span&gt;&lt;span&gt;        inventory_index: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;usize&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;        target: Coord,
&lt;&#x2F;span&gt;&lt;span&gt;        message_log: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Vec&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;LogMessage&amp;gt;,
&lt;&#x2F;span&gt;&lt;span&gt;    ) -&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Result&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;(), ()&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;match&lt;&#x2F;span&gt;&lt;span&gt; item_type {
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;            ItemType::FireballScroll &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;                &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; fireball &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span&gt;ProjectileType::Fireball {
&lt;&#x2F;span&gt;&lt;span&gt;                    damage: (&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;*&lt;&#x2F;span&gt;&lt;span&gt;self.components.intelligence.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;get&lt;&#x2F;span&gt;&lt;span&gt;(character).&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;unwrap&lt;&#x2F;span&gt;&lt;span&gt;()).&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;max&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt;) &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;as u32&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;                };
&lt;&#x2F;span&gt;&lt;span&gt;                message_log.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;push&lt;&#x2F;span&gt;&lt;span&gt;(LogMessage::PlayerLaunchesProjectile(fireball));
&lt;&#x2F;span&gt;&lt;span&gt;                self.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;spawn_projectile&lt;&#x2F;span&gt;&lt;span&gt;(character_coord, target, fireball);
&lt;&#x2F;span&gt;&lt;span&gt;            }
&lt;&#x2F;span&gt;&lt;span&gt;            ItemType::ConfusionScroll &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;                &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; confusion &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span&gt;ProjectileType::Confusion {
&lt;&#x2F;span&gt;&lt;span&gt;                    duration: (&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;*&lt;&#x2F;span&gt;&lt;span&gt;self.components.intelligence.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;get&lt;&#x2F;span&gt;&lt;span&gt;(character).&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;unwrap&lt;&#x2F;span&gt;&lt;span&gt;()).&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;max&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt;) &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;as u32
&lt;&#x2F;span&gt;&lt;span&gt;                        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;* &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;3&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;                };
&lt;&#x2F;span&gt;&lt;span&gt;                message_log.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;push&lt;&#x2F;span&gt;&lt;span&gt;(LogMessage::PlayerLaunchesProjectile(confusion));
&lt;&#x2F;span&gt;&lt;span&gt;                self.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;spawn_projectile&lt;&#x2F;span&gt;&lt;span&gt;(character_coord, target, confusion);
&lt;&#x2F;span&gt;&lt;span&gt;            }
&lt;&#x2F;span&gt;&lt;span&gt;        }
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Ok&lt;&#x2F;span&gt;&lt;span&gt;(())
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;When a projectile collides with a character, use the information in these fields to affect the character.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; world.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;impl &lt;&#x2F;span&gt;&lt;span&gt;World {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;pub fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;move_projectiles&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut &lt;&#x2F;span&gt;&lt;span&gt;self, message_log: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Vec&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;LogMessage&amp;gt;) {
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;let mut&lt;&#x2F;span&gt;&lt;span&gt; entities_to_remove &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Vec&lt;&#x2F;span&gt;&lt;span&gt;::new();
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;let mut&lt;&#x2F;span&gt;&lt;span&gt; fireball_hit &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Vec&lt;&#x2F;span&gt;&lt;span&gt;::new();
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;let mut&lt;&#x2F;span&gt;&lt;span&gt; confusion_hit &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Vec&lt;&#x2F;span&gt;&lt;span&gt;::new();
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;for &lt;&#x2F;span&gt;&lt;span&gt;(entity, trajectory) &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;in &lt;&#x2F;span&gt;&lt;span&gt;self.components.trajectory.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;iter_mut&lt;&#x2F;span&gt;&lt;span&gt;() {
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;if let &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Some&lt;&#x2F;span&gt;&lt;span&gt;(direction) &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; trajectory.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;next&lt;&#x2F;span&gt;&lt;span&gt;() {
&lt;&#x2F;span&gt;&lt;span&gt;                &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; current_coord &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span&gt;self.spatial_table.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;coord_of&lt;&#x2F;span&gt;&lt;span&gt;(entity).&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;unwrap&lt;&#x2F;span&gt;&lt;span&gt;();
&lt;&#x2F;span&gt;&lt;span&gt;                &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; new_coord &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; current_coord &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;+&lt;&#x2F;span&gt;&lt;span&gt; direction.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;coord&lt;&#x2F;span&gt;&lt;span&gt;();
&lt;&#x2F;span&gt;&lt;span&gt;                &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; dest_layers &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span&gt;self.spatial_table.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;layers_at_checked&lt;&#x2F;span&gt;&lt;span&gt;(new_coord);
&lt;&#x2F;span&gt;&lt;span&gt;                &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;if&lt;&#x2F;span&gt;&lt;span&gt; dest_layers.feature.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;is_some&lt;&#x2F;span&gt;&lt;span&gt;() {
&lt;&#x2F;span&gt;&lt;span&gt;                    entities_to_remove.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;push&lt;&#x2F;span&gt;&lt;span&gt;(entity);
&lt;&#x2F;span&gt;&lt;span&gt;                } &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;else if let &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Some&lt;&#x2F;span&gt;&lt;span&gt;(character) &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; dest_layers.character {
&lt;&#x2F;span&gt;&lt;span&gt;                    entities_to_remove.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;push&lt;&#x2F;span&gt;&lt;span&gt;(entity);
&lt;&#x2F;span&gt;&lt;span&gt;                    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;if let &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Some&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;projectile_type) &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span&gt;self.components.projectile.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;get&lt;&#x2F;span&gt;&lt;span&gt;(entity) {
&lt;&#x2F;span&gt;&lt;span&gt;                        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;match&lt;&#x2F;span&gt;&lt;span&gt; projectile_type {
&lt;&#x2F;span&gt;&lt;span&gt;                            ProjectileType::Fireball { damage } &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;                                fireball_hit.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;push&lt;&#x2F;span&gt;&lt;span&gt;((character, damage));
&lt;&#x2F;span&gt;&lt;span&gt;                            }
&lt;&#x2F;span&gt;&lt;span&gt;                            ProjectileType::Confusion { duration } &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;                                confusion_hit.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;push&lt;&#x2F;span&gt;&lt;span&gt;((character, duration));
&lt;&#x2F;span&gt;&lt;span&gt;                            }
&lt;&#x2F;span&gt;&lt;span&gt;                        }
&lt;&#x2F;span&gt;&lt;span&gt;                    }
&lt;&#x2F;span&gt;&lt;span&gt;                }
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;                &lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; ignore collisiosns of projectiles
&lt;&#x2F;span&gt;&lt;span&gt;                &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;let _ = &lt;&#x2F;span&gt;&lt;span&gt;self.spatial_table.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;update_coord&lt;&#x2F;span&gt;&lt;span&gt;(entity, new_coord);
&lt;&#x2F;span&gt;&lt;span&gt;            } &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;else &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;                entities_to_remove.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;push&lt;&#x2F;span&gt;&lt;span&gt;(entity);
&lt;&#x2F;span&gt;&lt;span&gt;            }
&lt;&#x2F;span&gt;&lt;span&gt;        }
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;for&lt;&#x2F;span&gt;&lt;span&gt; entity &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;in&lt;&#x2F;span&gt;&lt;span&gt; entities_to_remove {
&lt;&#x2F;span&gt;&lt;span&gt;            self.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;remove_entity&lt;&#x2F;span&gt;&lt;span&gt;(entity);
&lt;&#x2F;span&gt;&lt;span&gt;        }
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;for &lt;&#x2F;span&gt;&lt;span&gt;(entity, damage) &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;in&lt;&#x2F;span&gt;&lt;span&gt; fireball_hit {
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; maybe_npc &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span&gt;self.components.npc_type.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;get&lt;&#x2F;span&gt;&lt;span&gt;(entity).&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;cloned&lt;&#x2F;span&gt;&lt;span&gt;();
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;if let &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Some&lt;&#x2F;span&gt;&lt;span&gt;(VictimDies) &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span&gt;self.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;character_damage&lt;&#x2F;span&gt;&lt;span&gt;(entity, damage) {
&lt;&#x2F;span&gt;&lt;span&gt;                &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;if let &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Some&lt;&#x2F;span&gt;&lt;span&gt;(npc) &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; maybe_npc {
&lt;&#x2F;span&gt;&lt;span&gt;                    message_log.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;push&lt;&#x2F;span&gt;&lt;span&gt;(LogMessage::NpcDies(npc));
&lt;&#x2F;span&gt;&lt;span&gt;                }
&lt;&#x2F;span&gt;&lt;span&gt;            }
&lt;&#x2F;span&gt;&lt;span&gt;        }
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;for &lt;&#x2F;span&gt;&lt;span&gt;(entity, duration) &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;in&lt;&#x2F;span&gt;&lt;span&gt; confusion_hit {
&lt;&#x2F;span&gt;&lt;span&gt;            self.components.confusion_countdown.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;insert&lt;&#x2F;span&gt;&lt;span&gt;(entity, duration);
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;if let &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Some&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;npc_type) &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span&gt;self.components.npc_type.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;get&lt;&#x2F;span&gt;&lt;span&gt;(entity) {
&lt;&#x2F;span&gt;&lt;span&gt;                message_log.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;push&lt;&#x2F;span&gt;&lt;span&gt;(LogMessage::NpcBecomesConfused(npc_type));
&lt;&#x2F;span&gt;&lt;span&gt;            }
&lt;&#x2F;span&gt;&lt;span&gt;        }
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Now the higher your intelligence, the more powerful the effects of your spells.&lt;&#x2F;p&gt;
&lt;p&gt;Reference implementation branch: &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;gridbugs&#x2F;chargrid-roguelike-tutorial-2020&#x2F;tree&#x2F;part-11.3&quot;&gt;part-11.3&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;h2 id=&quot;upgrade-when-descending-stairs&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#upgrade-when-descending-stairs&quot; aria-label=&quot;Anchor link for: upgrade-when-descending-stairs&quot;&gt;Upgrade when Descending Stairs&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;Define a type to represent the different ways the player can level up.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; game.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;#[derive(Clone, Copy, Debug)]
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;pub enum &lt;&#x2F;span&gt;&lt;span&gt;LevelUp {
&lt;&#x2F;span&gt;&lt;span&gt;    Strength,
&lt;&#x2F;span&gt;&lt;span&gt;    Dexterity,
&lt;&#x2F;span&gt;&lt;span&gt;    Intelligence,
&lt;&#x2F;span&gt;&lt;span&gt;    Health,
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Add a method to &lt;code&gt;World&lt;&#x2F;code&gt; for leveling up a character.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; world.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;use crate&lt;&#x2F;span&gt;&lt;span&gt;::game::{ExamineCell, LevelUp, LogMessage};
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;impl &lt;&#x2F;span&gt;&lt;span&gt;World {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;pub fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;level_up_character&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut &lt;&#x2F;span&gt;&lt;span&gt;self, character_entity: Entity, level_up: LevelUp) {
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;match&lt;&#x2F;span&gt;&lt;span&gt; level_up {
&lt;&#x2F;span&gt;&lt;span&gt;            LevelUp::Strength &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;                &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;*&lt;&#x2F;span&gt;&lt;span&gt;self
&lt;&#x2F;span&gt;&lt;span&gt;                    .components
&lt;&#x2F;span&gt;&lt;span&gt;                    .strength
&lt;&#x2F;span&gt;&lt;span&gt;                    .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;get_mut&lt;&#x2F;span&gt;&lt;span&gt;(character_entity)
&lt;&#x2F;span&gt;&lt;span&gt;                    .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;expect&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;character lacks strength&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;) &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;+= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;;
&lt;&#x2F;span&gt;&lt;span&gt;            }
&lt;&#x2F;span&gt;&lt;span&gt;            LevelUp::Dexterity &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;                &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;*&lt;&#x2F;span&gt;&lt;span&gt;self
&lt;&#x2F;span&gt;&lt;span&gt;                    .components
&lt;&#x2F;span&gt;&lt;span&gt;                    .dexterity
&lt;&#x2F;span&gt;&lt;span&gt;                    .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;get_mut&lt;&#x2F;span&gt;&lt;span&gt;(character_entity)
&lt;&#x2F;span&gt;&lt;span&gt;                    .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;expect&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;character lacks dexterity&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;) &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;+= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;;
&lt;&#x2F;span&gt;&lt;span&gt;            }
&lt;&#x2F;span&gt;&lt;span&gt;            LevelUp::Intelligence &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;                &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;*&lt;&#x2F;span&gt;&lt;span&gt;self
&lt;&#x2F;span&gt;&lt;span&gt;                    .components
&lt;&#x2F;span&gt;&lt;span&gt;                    .intelligence
&lt;&#x2F;span&gt;&lt;span&gt;                    .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;get_mut&lt;&#x2F;span&gt;&lt;span&gt;(character_entity)
&lt;&#x2F;span&gt;&lt;span&gt;                    .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;expect&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;character lacks intelligence&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;) &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;+= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;;
&lt;&#x2F;span&gt;&lt;span&gt;            }
&lt;&#x2F;span&gt;&lt;span&gt;            LevelUp::Health &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;                &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; hit_points &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span&gt;self
&lt;&#x2F;span&gt;&lt;span&gt;                    .components
&lt;&#x2F;span&gt;&lt;span&gt;                    .hit_points
&lt;&#x2F;span&gt;&lt;span&gt;                    .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;get_mut&lt;&#x2F;span&gt;&lt;span&gt;(character_entity)
&lt;&#x2F;span&gt;&lt;span&gt;                    .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;expect&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;character lacks hit points&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;);
&lt;&#x2F;span&gt;&lt;span&gt;                &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;const &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;INCREASE&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;u32 = &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;5&lt;&#x2F;span&gt;&lt;span&gt;;
&lt;&#x2F;span&gt;&lt;span&gt;                hit_points.current &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;+= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;INCREASE&lt;&#x2F;span&gt;&lt;span&gt;;
&lt;&#x2F;span&gt;&lt;span&gt;                hit_points.max &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;+= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;INCREASE&lt;&#x2F;span&gt;&lt;span&gt;;
&lt;&#x2F;span&gt;&lt;span&gt;            }
&lt;&#x2F;span&gt;&lt;span&gt;        }
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Earlier in this section we defined a method &lt;code&gt;GameState::maybe_player_descend&lt;&#x2F;code&gt; which checked
whether the player is on the stairs, and if so, have them descend to the next dungeon level.
We now need to move this check outside of &lt;code&gt;GameState&lt;&#x2F;code&gt;. This is because when the player is on
the stairs and presses the ‘&amp;gt;’ key, we now want to display a level-up menu, which the player
may cancel. If they select stat to level up, only then will the player be taken to the next
level, and leveled-up, in a single atomic operation.&lt;&#x2F;p&gt;
&lt;p&gt;Thus replace &lt;code&gt;GameState::maybe_player_descend&lt;&#x2F;code&gt; with the following two methods:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; game.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;impl &lt;&#x2F;span&gt;&lt;span&gt;GameState {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;pub fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;player_level_up_and_descend&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut &lt;&#x2F;span&gt;&lt;span&gt;self, level_up: LevelUp) {
&lt;&#x2F;span&gt;&lt;span&gt;        assert!(self.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;is_player_on_stairs&lt;&#x2F;span&gt;&lt;span&gt;());
&lt;&#x2F;span&gt;&lt;span&gt;        self.world.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;level_up_character&lt;&#x2F;span&gt;&lt;span&gt;(self.player_entity, level_up);
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; player_data &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span&gt;self.world.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;remove_character&lt;&#x2F;span&gt;&lt;span&gt;(self.player_entity);
&lt;&#x2F;span&gt;&lt;span&gt;        self.world.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;clear&lt;&#x2F;span&gt;&lt;span&gt;();
&lt;&#x2F;span&gt;&lt;span&gt;        self.visibility_grid.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;clear&lt;&#x2F;span&gt;&lt;span&gt;();
&lt;&#x2F;span&gt;&lt;span&gt;        self.dungeon_level &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;+= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;;
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; Populate {
&lt;&#x2F;span&gt;&lt;span&gt;            player_entity,
&lt;&#x2F;span&gt;&lt;span&gt;            ai_state,
&lt;&#x2F;span&gt;&lt;span&gt;        } &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span&gt;self.world.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;populate&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut &lt;&#x2F;span&gt;&lt;span&gt;self.rng);
&lt;&#x2F;span&gt;&lt;span&gt;        self.world.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;replace_character&lt;&#x2F;span&gt;&lt;span&gt;(player_entity, player_data);
&lt;&#x2F;span&gt;&lt;span&gt;        self.player_entity &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; player_entity;
&lt;&#x2F;span&gt;&lt;span&gt;        self.ai_state &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; ai_state;
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;pub fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;is_player_on_stairs&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;self) -&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;bool &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;        self.world.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;coord_contains_stairs&lt;&#x2F;span&gt;&lt;span&gt;(self.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;player_coord&lt;&#x2F;span&gt;&lt;span&gt;())
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Now we need to describe the level-up menu.&lt;&#x2F;p&gt;
&lt;p&gt;Start with a function that creates a menu instance, listing all the different ways to level up.
Make it a &lt;code&gt;MenuInstanceChooseOrEscape&lt;&#x2F;code&gt; so that it’s sensitive to the escape key being pressed,
allowing the user to cancel the menu and not descend the stairs yet.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; app.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;use crate&lt;&#x2F;span&gt;&lt;span&gt;::game::{GameState, LevelUp};
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;level_up_menu_instance&lt;&#x2F;span&gt;&lt;span&gt;() -&amp;gt; MenuInstanceChooseOrEscape&amp;lt;LevelUp&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;use &lt;&#x2F;span&gt;&lt;span&gt;LevelUp::&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;*&lt;&#x2F;span&gt;&lt;span&gt;;
&lt;&#x2F;span&gt;&lt;span&gt;    MenuInstanceBuilder {
&lt;&#x2F;span&gt;&lt;span&gt;        items: vec![Strength, Dexterity, Intelligence, Health],
&lt;&#x2F;span&gt;&lt;span&gt;        hotkeys: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;None&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;        selected_index: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;    .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;build&lt;&#x2F;span&gt;&lt;span&gt;()
&lt;&#x2F;span&gt;&lt;span&gt;    .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;unwrap&lt;&#x2F;span&gt;&lt;span&gt;()
&lt;&#x2F;span&gt;&lt;span&gt;    .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;into_choose_or_escape&lt;&#x2F;span&gt;&lt;span&gt;()
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Describe how to render the level-up menu.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; app.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;#[derive(Default)]
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;struct &lt;&#x2F;span&gt;&lt;span&gt;LevelUpMenuView {
&lt;&#x2F;span&gt;&lt;span&gt;    mouse_tracker: MenuInstanceMouseTracker,
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;impl &lt;&#x2F;span&gt;&lt;span&gt;MenuIndexFromScreenCoord &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;for &lt;&#x2F;span&gt;&lt;span&gt;LevelUpMenuView {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;menu_index_from_screen_coord&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;self, len: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;usize&lt;&#x2F;span&gt;&lt;span&gt;, coord: Coord) -&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Option&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;usize&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;        self.mouse_tracker.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;menu_index_from_screen_coord&lt;&#x2F;span&gt;&lt;span&gt;(len, coord)
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;impl&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;#39;a&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt; View&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&amp;#39;a&lt;&#x2F;span&gt;&lt;span&gt; AppData&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;for &lt;&#x2F;span&gt;&lt;span&gt;LevelUpMenuView {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;view&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;F: Frame, C: ColModify&amp;gt;(
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut &lt;&#x2F;span&gt;&lt;span&gt;self,
&lt;&#x2F;span&gt;&lt;span&gt;        data: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&amp;#39;a&lt;&#x2F;span&gt;&lt;span&gt; AppData,
&lt;&#x2F;span&gt;&lt;span&gt;        context: ViewContext&amp;lt;C&amp;gt;,
&lt;&#x2F;span&gt;&lt;span&gt;        frame: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut&lt;&#x2F;span&gt;&lt;span&gt; F,
&lt;&#x2F;span&gt;&lt;span&gt;    ) {
&lt;&#x2F;span&gt;&lt;span&gt;        self.mouse_tracker.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;new_frame&lt;&#x2F;span&gt;&lt;span&gt;(context.offset);
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;for &lt;&#x2F;span&gt;&lt;span&gt;(i, &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;level_up, maybe_selected) &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;in&lt;&#x2F;span&gt;&lt;span&gt; data.level_up_menu.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;menu_instance&lt;&#x2F;span&gt;&lt;span&gt;().&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;enumerate&lt;&#x2F;span&gt;&lt;span&gt;() {
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;let &lt;&#x2F;span&gt;&lt;span&gt;(prefix, style) &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= if&lt;&#x2F;span&gt;&lt;span&gt; maybe_selected.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;is_some&lt;&#x2F;span&gt;&lt;span&gt;() {
&lt;&#x2F;span&gt;&lt;span&gt;                (
&lt;&#x2F;span&gt;&lt;span&gt;                    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;&amp;gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;                    Style::new()
&lt;&#x2F;span&gt;&lt;span&gt;                        .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;with_foreground&lt;&#x2F;span&gt;&lt;span&gt;(Rgb24::new_grey(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;255&lt;&#x2F;span&gt;&lt;span&gt;))
&lt;&#x2F;span&gt;&lt;span&gt;                        .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;with_bold&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;true&lt;&#x2F;span&gt;&lt;span&gt;),
&lt;&#x2F;span&gt;&lt;span&gt;                )
&lt;&#x2F;span&gt;&lt;span&gt;            } &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;else &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;                (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot; &amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;, Style::new().&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;with_foreground&lt;&#x2F;span&gt;&lt;span&gt;(Rgb24::new_grey(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;187&lt;&#x2F;span&gt;&lt;span&gt;)))
&lt;&#x2F;span&gt;&lt;span&gt;            };
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; text &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= match&lt;&#x2F;span&gt;&lt;span&gt; level_up {
&lt;&#x2F;span&gt;&lt;span&gt;                LevelUp::Strength &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;Strength&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;                LevelUp::Dexterity &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;Dexterity&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;                LevelUp::Intelligence &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;Intelligence&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;                LevelUp::Health &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;Constitution&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;            };
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; size &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span&gt;StringViewSingleLine::new(style).&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;view_size&lt;&#x2F;span&gt;&lt;span&gt;(
&lt;&#x2F;span&gt;&lt;span&gt;                format!(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;{} {}&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;, prefix, text),
&lt;&#x2F;span&gt;&lt;span&gt;                context.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;add_offset&lt;&#x2F;span&gt;&lt;span&gt;(Coord::new(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt;, i &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;as i32&lt;&#x2F;span&gt;&lt;span&gt;)),
&lt;&#x2F;span&gt;&lt;span&gt;                frame,
&lt;&#x2F;span&gt;&lt;span&gt;            );
&lt;&#x2F;span&gt;&lt;span&gt;            self.mouse_tracker.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;on_entry_view_size&lt;&#x2F;span&gt;&lt;span&gt;(size);
&lt;&#x2F;span&gt;&lt;span&gt;        }
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Describe a “selector” for the menu that selects the fields of &lt;code&gt;AppData&lt;&#x2F;code&gt; and &lt;code&gt;AppView&lt;&#x2F;code&gt; for
using and rendering the menu.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; app.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;struct &lt;&#x2F;span&gt;&lt;span&gt;LevelUpMenuSelect;
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;impl &lt;&#x2F;span&gt;&lt;span&gt;ChooseSelector &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;for &lt;&#x2F;span&gt;&lt;span&gt;LevelUpMenuSelect {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;type &lt;&#x2F;span&gt;&lt;span&gt;ChooseOutput &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span&gt;MenuInstanceChooseOrEscape&amp;lt;LevelUp&amp;gt;;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;choose_mut&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;#39;a&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;self, input: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&amp;#39;a mut Self::&lt;&#x2F;span&gt;&lt;span&gt;DataInput) -&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&amp;#39;a mut Self::&lt;&#x2F;span&gt;&lt;span&gt;ChooseOutput {
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut&lt;&#x2F;span&gt;&lt;span&gt; input.level_up_menu
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;impl &lt;&#x2F;span&gt;&lt;span&gt;DataSelector &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;for &lt;&#x2F;span&gt;&lt;span&gt;LevelUpMenuSelect {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;type &lt;&#x2F;span&gt;&lt;span&gt;DataInput &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; AppData;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;type &lt;&#x2F;span&gt;&lt;span&gt;DataOutput &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; AppData;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;data&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;#39;a&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;self, input: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&amp;#39;a Self::&lt;&#x2F;span&gt;&lt;span&gt;DataInput) -&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&amp;#39;a Self::&lt;&#x2F;span&gt;&lt;span&gt;DataOutput {
&lt;&#x2F;span&gt;&lt;span&gt;        input
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;data_mut&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;#39;a&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;self, input: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&amp;#39;a mut Self::&lt;&#x2F;span&gt;&lt;span&gt;DataInput) -&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&amp;#39;a mut Self::&lt;&#x2F;span&gt;&lt;span&gt;DataOutput {
&lt;&#x2F;span&gt;&lt;span&gt;        input
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;impl &lt;&#x2F;span&gt;&lt;span&gt;ViewSelector &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;for &lt;&#x2F;span&gt;&lt;span&gt;LevelUpMenuSelect {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;type &lt;&#x2F;span&gt;&lt;span&gt;ViewInput &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; AppView;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;type &lt;&#x2F;span&gt;&lt;span&gt;ViewOutput &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; LevelUpMenuView;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;view&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;#39;a&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;self, input: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&amp;#39;a Self::&lt;&#x2F;span&gt;&lt;span&gt;ViewInput) -&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&amp;#39;a Self::&lt;&#x2F;span&gt;&lt;span&gt;ViewOutput {
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;input.level_up_menu_view
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;view_mut&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;#39;a&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;self, input: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&amp;#39;a mut Self::&lt;&#x2F;span&gt;&lt;span&gt;ViewInput) -&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&amp;#39;a mut Self::&lt;&#x2F;span&gt;&lt;span&gt;ViewOutput {
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut&lt;&#x2F;span&gt;&lt;span&gt; input.level_up_menu_view
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Define a decorator which knows how to render the menu on top of the game, dimming the game in the background.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; app.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;struct &lt;&#x2F;span&gt;&lt;span&gt;LevelUpMenuDecorate;
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;impl &lt;&#x2F;span&gt;&lt;span&gt;Decorate &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;for &lt;&#x2F;span&gt;&lt;span&gt;LevelUpMenuDecorate {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;type &lt;&#x2F;span&gt;&lt;span&gt;View &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; AppView;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;type &lt;&#x2F;span&gt;&lt;span&gt;Data &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; AppData;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;view&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;E, F, C&amp;gt;(
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;self,
&lt;&#x2F;span&gt;&lt;span&gt;        data: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;Self::&lt;&#x2F;span&gt;&lt;span&gt;Data,
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;mut &lt;&#x2F;span&gt;&lt;span&gt;event_routine_view: EventRoutineView&amp;lt;E&amp;gt;,
&lt;&#x2F;span&gt;&lt;span&gt;        context: ViewContext&amp;lt;C&amp;gt;,
&lt;&#x2F;span&gt;&lt;span&gt;        frame: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut&lt;&#x2F;span&gt;&lt;span&gt; F,
&lt;&#x2F;span&gt;&lt;span&gt;    ) &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;where
&lt;&#x2F;span&gt;&lt;span&gt;        E: EventRoutine&amp;lt;Data = &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;Self::&lt;&#x2F;span&gt;&lt;span&gt;Data, View = &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;Self::&lt;&#x2F;span&gt;&lt;span&gt;View&amp;gt;,
&lt;&#x2F;span&gt;&lt;span&gt;        F: Frame,
&lt;&#x2F;span&gt;&lt;span&gt;        C: ColModify,
&lt;&#x2F;span&gt;&lt;span&gt;    {
&lt;&#x2F;span&gt;&lt;span&gt;        BoundView {
&lt;&#x2F;span&gt;&lt;span&gt;            size: data.game_state.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;size&lt;&#x2F;span&gt;&lt;span&gt;(),
&lt;&#x2F;span&gt;&lt;span&gt;            view: AlignView {
&lt;&#x2F;span&gt;&lt;span&gt;                alignment: Alignment::centre(),
&lt;&#x2F;span&gt;&lt;span&gt;                view: FillBackgroundView {
&lt;&#x2F;span&gt;&lt;span&gt;                    rgb24: Rgb24::new_grey(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt;),
&lt;&#x2F;span&gt;&lt;span&gt;                    view: BorderView {
&lt;&#x2F;span&gt;&lt;span&gt;                        style: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;BorderStyle {
&lt;&#x2F;span&gt;&lt;span&gt;                            title: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Some&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;Level Up&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;to_string&lt;&#x2F;span&gt;&lt;span&gt;()),
&lt;&#x2F;span&gt;&lt;span&gt;                            title_style: Style::new().&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;with_foreground&lt;&#x2F;span&gt;&lt;span&gt;(Rgb24::new_grey(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;255&lt;&#x2F;span&gt;&lt;span&gt;)),
&lt;&#x2F;span&gt;&lt;span&gt;                            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;..&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Default&lt;&#x2F;span&gt;&lt;span&gt;::default()
&lt;&#x2F;span&gt;&lt;span&gt;                        },
&lt;&#x2F;span&gt;&lt;span&gt;                        view: MinSizeView {
&lt;&#x2F;span&gt;&lt;span&gt;                            size: Size::new(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;12&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt;),
&lt;&#x2F;span&gt;&lt;span&gt;                            view: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut&lt;&#x2F;span&gt;&lt;span&gt; event_routine_view,
&lt;&#x2F;span&gt;&lt;span&gt;                        },
&lt;&#x2F;span&gt;&lt;span&gt;                    },
&lt;&#x2F;span&gt;&lt;span&gt;                },
&lt;&#x2F;span&gt;&lt;span&gt;            },
&lt;&#x2F;span&gt;&lt;span&gt;        }
&lt;&#x2F;span&gt;&lt;span&gt;        .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;view&lt;&#x2F;span&gt;&lt;span&gt;(data, context.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;add_depth&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;10&lt;&#x2F;span&gt;&lt;span&gt;), frame);
&lt;&#x2F;span&gt;&lt;span&gt;        event_routine_view.view.game_view.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;view&lt;&#x2F;span&gt;&lt;span&gt;(
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;data.game_state,
&lt;&#x2F;span&gt;&lt;span&gt;            context.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;compose_col_modify&lt;&#x2F;span&gt;&lt;span&gt;(ColModifyMap(|c: Rgb24| c.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;saturating_scalar_mul_div&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;2&lt;&#x2F;span&gt;&lt;span&gt;))),
&lt;&#x2F;span&gt;&lt;span&gt;            frame,
&lt;&#x2F;span&gt;&lt;span&gt;        );
&lt;&#x2F;span&gt;&lt;span&gt;        event_routine_view
&lt;&#x2F;span&gt;&lt;span&gt;            .view
&lt;&#x2F;span&gt;&lt;span&gt;            .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;render_ui&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;None&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;data, context, frame);
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;And define a function returning an &lt;code&gt;impl EventRoutine&amp;lt;...&amp;gt;&lt;&#x2F;code&gt; which runs the menu
and “returns” the level-up or cancellation selected by the player.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; app.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;level_up_menu&lt;&#x2F;span&gt;&lt;span&gt;() -&amp;gt; impl EventRoutine&amp;lt;
&lt;&#x2F;span&gt;&lt;span&gt;    Return = &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Result&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;LevelUp, menu::Escape&amp;gt;,
&lt;&#x2F;span&gt;&lt;span&gt;    Data = AppData,
&lt;&#x2F;span&gt;&lt;span&gt;    View = AppView,
&lt;&#x2F;span&gt;&lt;span&gt;    Event = CommonEvent,
&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;    MenuInstanceRoutine::new(LevelUpMenuSelect)
&lt;&#x2F;span&gt;&lt;span&gt;        .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;convert_input_to_common_event&lt;&#x2F;span&gt;&lt;span&gt;()
&lt;&#x2F;span&gt;&lt;span&gt;        .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;decorated&lt;&#x2F;span&gt;&lt;span&gt;(LevelUpMenuDecorate)
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Phew. Developer note: Adding menus currently requires a lot of boilerplate. The common patterns should be encapsulated
into helpers inside the &lt;code&gt;chargrid&lt;&#x2F;code&gt; library.&lt;&#x2F;p&gt;
&lt;p&gt;Add the data and view fields to &lt;code&gt;AppData&lt;&#x2F;code&gt; and &lt;code&gt;AppView&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; app.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;struct &lt;&#x2F;span&gt;&lt;span&gt;AppData {
&lt;&#x2F;span&gt;&lt;span&gt;    ...
&lt;&#x2F;span&gt;&lt;span&gt;    level_up_menu: MenuInstanceChooseOrEscape&amp;lt;LevelUp&amp;gt;,
&lt;&#x2F;span&gt;&lt;span&gt;    ...
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;impl &lt;&#x2F;span&gt;&lt;span&gt;AppData {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;new&lt;&#x2F;span&gt;&lt;span&gt;(screen_size: Size, rng_seed: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;u64&lt;&#x2F;span&gt;&lt;span&gt;, visibility_algorithm: VisibilityAlgorithm) -&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;Self &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;Self &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;            level_up_menu: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;level_up_menu_instance&lt;&#x2F;span&gt;&lt;span&gt;(),
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;        }
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;struct &lt;&#x2F;span&gt;&lt;span&gt;AppView {
&lt;&#x2F;span&gt;&lt;span&gt;    ...
&lt;&#x2F;span&gt;&lt;span&gt;    level_up_menu_view: LevelUpMenuView,
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;impl &lt;&#x2F;span&gt;&lt;span&gt;AppView {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;new&lt;&#x2F;span&gt;&lt;span&gt;(screen_size: Size) -&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;Self &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;Self &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;            level_up_menu_view: LevelUpMenuView::default(),
&lt;&#x2F;span&gt;&lt;span&gt;        }
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Update &lt;code&gt;AppData::handle_input&lt;&#x2F;code&gt; such that when ‘&amp;gt;’ is pressed, rather than immediately descending,
return a new &lt;code&gt;GameReturn&lt;&#x2F;code&gt; representing the fact that a level-up menu should be run.
Also add a helper function for applying the &lt;code&gt;LevelUp&lt;&#x2F;code&gt; and descending the player.
Run the visibility system after descending so that the next time the game state is rendered
there are visible cells from the new level.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; app.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;enum &lt;&#x2F;span&gt;&lt;span&gt;GameReturn {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;    LevelUpAndDescend,
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;impl &lt;&#x2F;span&gt;&lt;span&gt;AppData {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;handle_input&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut &lt;&#x2F;span&gt;&lt;span&gt;self, input: Input) -&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Option&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;GameReturn&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;match&lt;&#x2F;span&gt;&lt;span&gt; input {
&lt;&#x2F;span&gt;&lt;span&gt;            Input::Keyboard(key) &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;                &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;match&lt;&#x2F;span&gt;&lt;span&gt; key {
&lt;&#x2F;span&gt;&lt;span&gt;                    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;                    KeyboardInput::Char(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;#39;&amp;gt;&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;) &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;                        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;if &lt;&#x2F;span&gt;&lt;span&gt;self.game_state.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;is_player_on_stairs&lt;&#x2F;span&gt;&lt;span&gt;() {
&lt;&#x2F;span&gt;&lt;span&gt;                            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;return &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Some&lt;&#x2F;span&gt;&lt;span&gt;(GameReturn::LevelUpAndDescend);
&lt;&#x2F;span&gt;&lt;span&gt;                        }
&lt;&#x2F;span&gt;&lt;span&gt;                    }
&lt;&#x2F;span&gt;&lt;span&gt;                    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;                }
&lt;&#x2F;span&gt;&lt;span&gt;                &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;            }
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;        }
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;player_level_up_and_descend&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut &lt;&#x2F;span&gt;&lt;span&gt;self, level_up: LevelUp) {
&lt;&#x2F;span&gt;&lt;span&gt;        self.game_state.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;player_level_up_and_descend&lt;&#x2F;span&gt;&lt;span&gt;(level_up);
&lt;&#x2F;span&gt;&lt;span&gt;        self.game_state.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;update_visibility&lt;&#x2F;span&gt;&lt;span&gt;(self.visibility_algorithm);
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;And update &lt;code&gt;game_loop&lt;&#x2F;code&gt; to handle &lt;code&gt;LevelUpAndDescend&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; app.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;game_loop&lt;&#x2F;span&gt;&lt;span&gt;() -&amp;gt; impl EventRoutine&amp;lt;Return = (), Data = AppData, View = AppView, Event = CommonEvent&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;    make_either!(Ei &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; A &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;|&lt;&#x2F;span&gt;&lt;span&gt; B &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;|&lt;&#x2F;span&gt;&lt;span&gt; C &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;|&lt;&#x2F;span&gt;&lt;span&gt; D &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;|&lt;&#x2F;span&gt;&lt;span&gt; E &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;|&lt;&#x2F;span&gt;&lt;span&gt; F);
&lt;&#x2F;span&gt;&lt;span&gt;    Loop::new(|| {
&lt;&#x2F;span&gt;&lt;span&gt;        GameEventRoutine.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;and_then&lt;&#x2F;span&gt;&lt;span&gt;(|game_return| &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;match&lt;&#x2F;span&gt;&lt;span&gt; game_return {
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;            GameReturn::LevelUpAndDescend &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span&gt;Ei::F(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;level_up_menu&lt;&#x2F;span&gt;&lt;span&gt;().&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;and_then&lt;&#x2F;span&gt;&lt;span&gt;(|maybe_level_up| {
&lt;&#x2F;span&gt;&lt;span&gt;                SideEffect::new_with_view(&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;move |&lt;&#x2F;span&gt;&lt;span&gt;data: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut&lt;&#x2F;span&gt;&lt;span&gt; AppData, &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;_&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;_| &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;                    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;match&lt;&#x2F;span&gt;&lt;span&gt; maybe_level_up {
&lt;&#x2F;span&gt;&lt;span&gt;                        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Err&lt;&#x2F;span&gt;&lt;span&gt;(menu::Escape) &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span&gt;(),
&lt;&#x2F;span&gt;&lt;span&gt;                        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Ok&lt;&#x2F;span&gt;&lt;span&gt;(level_up) &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt; data.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;player_level_up_and_descend&lt;&#x2F;span&gt;&lt;span&gt;(level_up),
&lt;&#x2F;span&gt;&lt;span&gt;                    }
&lt;&#x2F;span&gt;&lt;span&gt;                    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;None
&lt;&#x2F;span&gt;&lt;span&gt;                })
&lt;&#x2F;span&gt;&lt;span&gt;            })),
&lt;&#x2F;span&gt;&lt;span&gt;        })
&lt;&#x2F;span&gt;&lt;span&gt;    })
&lt;&#x2F;span&gt;&lt;span&gt;    .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;return_on_exit&lt;&#x2F;span&gt;&lt;span&gt;(|data| data.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;save_game&lt;&#x2F;span&gt;&lt;span&gt;())
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Now when the player hits ‘&amp;gt;’ while standing on the stairs, they see a menu like this:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;roguelike-tutorial-2020-part-11&#x2F;screenshot-end.png&quot; alt=&quot;screenshot-end.png&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;If they make a selection, the relevant stat will increase, and they’ll descend to the next dungeon level.&lt;&#x2F;p&gt;
&lt;p&gt;Reference implementation branch: &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;gridbugs&#x2F;chargrid-roguelike-tutorial-2020&#x2F;tree&#x2F;part-11.4&quot;&gt;part-11.4&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;a href=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;roguelike-tutorial-2020-part-12&#x2F;&quot;&gt;Click here for the next part!&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Time to stop using language managers</title>
          <pubDate>Sun, 09 Aug 2020 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/daily/time-to-stop-using-language-managers/</link>
          <guid>https://www.gridbugs.org/daily/time-to-stop-using-language-managers/</guid>
          <description xml:base="https://www.gridbugs.org/daily/time-to-stop-using-language-managers/">&lt;p&gt;A few days ago I listed some problems I need to solve before I consider switching to OpenBSD in earnest.
Getting nodejs to compile with &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;nvm-sh&#x2F;nvm&quot;&gt;the nodejs version manager&lt;&#x2F;a&gt; was one such problem.
NVM is a tool for managing the concurrent installation of multiple different versions of nodejs.
When possible it downloads binary distributions of nodejs, but in uncommon environments such as OpenBSD
it builds nodejs from source.&lt;&#x2F;p&gt;
&lt;p&gt;Without modifications to its source code, nodejs does not build on OpenBSD.
Last night I figured out how to get nodejs to build.
There’s a couple of places where it assumes that FreeBSD is the only BSD.
Only after getting it working did it occur to
me to just look in the &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;openbsd&#x2F;ports&#x2F;tree&#x2F;master&#x2F;lang&#x2F;node&quot;&gt;ports tree&lt;&#x2F;a&gt;
which contains the patches necessary to get nodejs to build on OpenBSD.&lt;&#x2F;p&gt;
&lt;p&gt;I don’t want to let language vendors dictate my choice of operating system.
The port maintainers go to the trouble of patching language implementations for OpenBSD,
and I don’t want to duplicate their efforts. If I’m not going to use nvm I may as well just use
the binary nodejs package for OpenBSD.&lt;&#x2F;p&gt;
&lt;p&gt;That leaves the question of what to do about projects
that depend on old versions old nodejs. I only have a handful of these (&lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;gridbugs&#x2F;roguelike-lighting-demo&#x2F;&quot;&gt;such as this one&lt;&#x2F;a&gt;),
and I’m not actively working on any of them. It would just be nice if they continued to compile.
That said, I don’t expect to need to build them very often, so going out of my way to make the process easy
by using a version manager is not worth it. &lt;em&gt;If&lt;&#x2F;em&gt; I ever need to build these projects again,
I can just compile the correct version of nodejs myself (possibly using patches from the ports tree as a reference),
or just run a different OS in a virtual machine.&lt;&#x2F;p&gt;
&lt;p&gt;I also use &lt;a href=&quot;http:&#x2F;&#x2F;rvm.io&#x2F;&quot;&gt;the ruby version manager&lt;&#x2F;a&gt; to manage installations of ruby.
The only reason I started using this was to get around some long forgotten problem I had
years ago installing ruby with apt on ubuntu.
The only purpose I have for ruby is this website. It’s very simple and easy to keep up to date, so
managing multiple ruby versions is not something I need. The ruby packages in FreeBSD and OpenBSD both
work perfectly fine, so bye bye rvm.&lt;&#x2F;p&gt;
&lt;p&gt;Finally there’s &lt;a href=&quot;https:&#x2F;&#x2F;rustup.rs&#x2F;&quot;&gt;rustup&lt;&#x2F;a&gt;. This one’s a little harder to live without.
On OpenBSD this decision is made for me, as the rust project doesn’t have binaries available
anyway. The problem is that people who use rust (myself included) get really excited about
new language features, and start using them in their projects right away.
OpenBSD has binary rust packages available, but at the time of writing they’re several
versions out of date (at the time of writing 1.42 compared to 1.45).
The rust language server &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;rust-analyzer&#x2F;rust-analyzer&quot;&gt;rust-analyser&lt;&#x2F;a&gt;
requires 1.43 of the compiler. I also occasionally rely on the ability to switch back and
forth between the stable and nightly versions of the rust compiler to enable experimental features.&lt;&#x2F;p&gt;
&lt;p&gt;I’ll stop using nvm and rvm which will greatly simplify my setup. I’ll continue to use rustup, and look forward to it becoming
available on OpenBSD.&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Roguelike Tutorial - Levelling up the player character</title>
          <pubDate>Sat, 08 Aug 2020 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/daily/roguelike-tutorial-levelling-up-the-player-character/</link>
          <guid>https://www.gridbugs.org/daily/roguelike-tutorial-levelling-up-the-player-character/</guid>
          <description xml:base="https://www.gridbugs.org/daily/roguelike-tutorial-levelling-up-the-player-character/">&lt;p&gt;I’m working on a series of roguelike tutorials as part of
&lt;a href=&quot;https:&#x2F;&#x2F;old.reddit.com&#x2F;r&#x2F;roguelikedev&#x2F;wiki&#x2F;python_tutorial_series&quot;&gt;roguelikedev subreddit does the complete
roguelike tutorial&lt;&#x2F;a&gt;.
Today I updated the game so when the player descends to the next level of
the dungeon they are presented with a “level up” menu.
This required adding some stats like strength and intelligence.
For the first time the game feels like a real game.&lt;&#x2F;p&gt;
&lt;p&gt;I’ll post the tutorial pages in a couple of days.&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>OpenBSD</title>
          <pubDate>Fri, 07 Aug 2020 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/daily/openbsd/</link>
          <guid>https://www.gridbugs.org/daily/openbsd/</guid>
          <description xml:base="https://www.gridbugs.org/daily/openbsd/">&lt;p&gt;I’ve been toying around with OpenBSD in a virtual machine to see whether I could
switch to it as a daily driver. I’m attracted to the project because of its
emphasis on minimalism and security. There’s a few things I need to figure out
before I can be as productive in OpenBSD as I am in Linux (which I use in most places)
or FreeBSD (which I recently switched to on my personal laptop).&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;the rust compiler tools aren’t available from the rust project (via &lt;a href=&quot;https:&#x2F;&#x2F;rustup.rs&#x2F;&quot;&gt;rustup&lt;&#x2F;a&gt;)
compiled for OpenBSD (but rustc and cargo are in the package repository, just several versions out of date)&lt;&#x2F;li&gt;
&lt;li&gt;the popular rust audio library &lt;a href=&quot;https:&#x2F;&#x2F;crates.io&#x2F;crates&#x2F;cpal&quot;&gt;cpal&lt;&#x2F;a&gt; doesn’t appear to support OpenBSD.
Currently it depends on alsa on unixes, which works on Linux, and FreeBSD (which emulates alsa) but
OpenBSD has no interest in Linux compatibility (which is fair enough!).&lt;&#x2F;li&gt;
&lt;li&gt;nodejs doesn’t compile in its default configuration as it depends on libdl which doesn’t exist in OpenBSD.
The only reason it exists in FreeBSD for (again) compatibility with Linux.
I use &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;nvm-sh&#x2F;nvm&quot;&gt;nvm&lt;&#x2F;a&gt;
to manage multiple versions of nodejs, and it builds nodejs from source when no binary distribution is
available. You can pass configuration flags to nvm which it passes on to
nodejs’s build system. Just got to work out the right ones to get it to build on OpenBSD.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;I can live without rustup. It also wouldn’t surprise me if it got support for OpenBSD at some point.
I’ll probably have to add OpenBSD support to cpal as alsa isn’t (and shouldn’t) be available on OpenBSD.
There’s got to be a way to configure nodejs to build on OpenBSD. Need to do some more reading.
These aren’t problems on FreeBSD because FreeBSD tries to be compatible with Linux.
The lack of this on OpenBSD is a selling point, but it does come at a cost of convenience.
There’s a few things I still need to learn before I can live with this inconvenience.&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>stevebob.net</title>
          <pubDate>Thu, 06 Aug 2020 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/daily/stevebob-net/</link>
          <guid>https://www.gridbugs.org/daily/stevebob-net/</guid>
          <description xml:base="https://www.gridbugs.org/daily/stevebob-net/">&lt;p&gt;Back in 2010 (which wow - over 10 years ago!) I made a little website
&lt;a href=&quot;https:&#x2F;&#x2F;stevebob.net&quot;&gt;stevebob.net&lt;&#x2F;a&gt; for storiing hobby projects - mostly
graphical demos written in javascript. I spent about 5 years posting demos,
and later blog posts, on stevebob.net. I haven’t touched it since 2014,
which was around the time I started gridbugs.org (originally takestairs.org)
to post about game development.&lt;&#x2F;p&gt;
&lt;p&gt;Last night I was finishing migrating gridbugs.org to
&lt;a href=&quot;https:&#x2F;&#x2F;www.nearlyfreespeech.net&#x2F;&quot;&gt;nearlyfreespeech&lt;&#x2F;a&gt;,
and I spotted the stevebob.net s3 bucket and decided to migrate it here as well.
I did some minor touch ups, removed some out-of-date details, and fixed
the easter egg on the home page where there’s an interactive console
under the site heading.&lt;&#x2F;p&gt;
&lt;p&gt;I also wrote one last post - a sign off - so when you visit the site the first
thing you see isn’t the last thing I happened to post before I stopped updating it.
I made my own CMS for stevebob.net because at the time I wanted to do everything
from scratch. The CMS is a bunch of ancient ruby, shell, make, and probably other
scripts cobbled together that generate static html. I’m not even going to attempt to run these.
Fortunately the static html files generated last time I ran the scripts are handy,
so I just added links to my final post to the relevant files.&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Replace all the CSS!</title>
          <pubDate>Wed, 05 Aug 2020 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/daily/replace-all-the-css/</link>
          <guid>https://www.gridbugs.org/daily/replace-all-the-css/</guid>
          <description xml:base="https://www.gridbugs.org/daily/replace-all-the-css/">&lt;p&gt;Last night I rewrote all the CSS in this site.
This site started its life with the &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;jekyll&#x2F;minima&quot;&gt;minima&lt;&#x2F;a&gt;
theme, and I’ve been gradually replacing parts until there’s almost nothing
of the original templates left. I’d been hacking on its original CSS for
years and it was growing into a tangled mess which I only partially understood.&lt;&#x2F;p&gt;
&lt;p&gt;So I deleted all the stylesheets (except syntax highlighting) and all the &lt;code&gt;class&lt;&#x2F;code&gt;
tags, and most &lt;code&gt;div&lt;&#x2F;code&gt; wrapper elements I was using for style, then rebuilt the
style to be the bare minimum to make the site look basically the same as before.
I now understand why every CSS rule is the way it is, and I can change the rules
in the future with confidence.&lt;&#x2F;p&gt;
&lt;p&gt;The hardest part was getting the nav bar along the top to behave sensibly when
the screen is narrow (e.g. on a phone). Previously it would replace the links
with a drop-down menu using a trick
involving a checkbox input element which I only learnt about when I viewed
the site without any CSS and still don’t understand how it worked.
In the interest of making the CSS simpler, I got rid of the drop-down menu
and now the nav links wrap around and have some minor style changes
when the screen width gets below a certain value.&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>I&#x27;ll probably move this site to nearlyfreespeech</title>
          <pubDate>Tue, 04 Aug 2020 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/daily/i-ll-probably-move-this-site-to-nearlyfreespeech/</link>
          <guid>https://www.gridbugs.org/daily/i-ll-probably-move-this-site-to-nearlyfreespeech/</guid>
          <description xml:base="https://www.gridbugs.org/daily/i-ll-probably-move-this-site-to-nearlyfreespeech/">&lt;p&gt;I recently grew frustrated with the infrastructure running this website.
At the time of writing it’s hosted on amazon s3, using cloudfront for
caching and managing certificates. S3 seems to have some issues with mime
types requiring occasional manual fixups through the aws console and
explicit cache invalidations. I’m sure (or at least I hope) that there’s a
way to configure aws to better suite my needs, but I’m also not &lt;em&gt;in love&lt;&#x2F;em&gt; with
amazon as a company, so I’ve started exploring alternatives.&lt;&#x2F;p&gt;
&lt;p&gt;This site is about as simple as a website can be - it’s a directory of
static html files (generated with jekyll, but the server doesn’t need
to worry about that), so I can host it pretty much anywhere.
I’ve been messing around with &lt;a href=&quot;https:&#x2F;&#x2F;www.nearlyfreespeech.net&#x2F;&quot;&gt;nearlyfreespeech&lt;&#x2F;a&gt;
and even set up a &lt;a href=&quot;https:&#x2F;&#x2F;gridbugs.nfshost.com&#x2F;&quot;&gt;version of this site&lt;&#x2F;a&gt;
there to see how easy it would be to set up and I can only describe the
process as “no nonsense”!&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Part 10 - Saving and Loading</title>
          <pubDate>Mon, 03 Aug 2020 19:00:00 +1000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/roguelike-tutorial-2020-part-10/</link>
          <guid>https://www.gridbugs.org/roguelike-tutorial-2020-part-10/</guid>
          <description xml:base="https://www.gridbugs.org/roguelike-tutorial-2020-part-10/">&lt;p&gt;In this part we’ll make it possible to save and load games, and add a main menu.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;roguelike-tutorial-2020-part-10&#x2F;screenshot-end.png&quot; alt=&quot;screenshot-end.png&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;This part is loosely based on &lt;a href=&quot;http:&#x2F;&#x2F;rogueliketutorials.com&#x2F;tutorials&#x2F;tcod&#x2F;part-10&#x2F;&quot;&gt;this part&lt;&#x2F;a&gt; of the
python tcod tutorial.&lt;&#x2F;p&gt;
&lt;p&gt;Reference implementation branch for starting point: &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;gridbugs&#x2F;chargrid-roguelike-tutorial-2020&#x2F;tree&#x2F;part-9-end&quot;&gt;part-9-end&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;p&gt;In this post:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;roguelike-tutorial-2020-part-10&#x2F;#serializing-game-state&quot;&gt;Serializing Game State&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;roguelike-tutorial-2020-part-10&#x2F;#main-menu&quot;&gt;Main Menu&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;roguelike-tutorial-2020-part-10&#x2F;#saving&quot;&gt;Saving&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;roguelike-tutorial-2020-part-10&#x2F;#loading&quot;&gt;Loading&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;serializing-game-state&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#serializing-game-state&quot; aria-label=&quot;Anchor link for: serializing-game-state&quot;&gt;Serializing Game State&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;In order to save the game, we must describe a way to convert the &lt;code&gt;GameState&lt;&#x2F;code&gt;
type to and from a sequence of bytes which can be stored in a file.
In rust, the typical way to do this is using a crate called &lt;a href=&quot;https:&#x2F;&#x2F;crates.io&#x2F;crates&#x2F;serde&quot;&gt;serde&lt;&#x2F;a&gt;.
It defines traits &lt;code&gt;Serialize&lt;&#x2F;code&gt; and &lt;code&gt;Deserialize&lt;&#x2F;code&gt;, which can be derived on a type
just like &lt;code&gt;Clone&lt;&#x2F;code&gt;, &lt;code&gt;Copy&lt;&#x2F;code&gt;, &lt;code&gt;Debug&lt;&#x2F;code&gt;, etc.&lt;&#x2F;p&gt;
&lt;p&gt;Many of the crates our game depend on can be configured to use serde to make the types they define
implement &lt;code&gt;Serialize&lt;&#x2F;code&gt; and &lt;code&gt;Deserilaize&lt;&#x2F;code&gt;. The &lt;code&gt;GameState&lt;&#x2F;code&gt; type contains many types imported from
crates. The first step is to configure these crates to allow the types they define to be serialized.
Update the &lt;code&gt;[dependencies]&lt;&#x2F;code&gt; section of &lt;code&gt;Cargo.toml&lt;&#x2F;code&gt; to look like this:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;toml&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-toml &quot;&gt;&lt;code class=&quot;language-toml&quot; data-lang=&quot;toml&quot;&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;# Cargo.toml
&lt;&#x2F;span&gt;&lt;span style=&quot;background-color:#f5f5f5;font-weight:bold;color:#b52a1d;&quot;&gt;...&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;[dependencies]
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#63a35c;&quot;&gt;chargrid_graphical &lt;&#x2F;span&gt;&lt;span&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;0.7&amp;quot;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#63a35c;&quot;&gt;chargrid &lt;&#x2F;span&gt;&lt;span&gt;= { &lt;&#x2F;span&gt;&lt;span style=&quot;color:#63a35c;&quot;&gt;version &lt;&#x2F;span&gt;&lt;span&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;0.4&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#63a35c;&quot;&gt;features &lt;&#x2F;span&gt;&lt;span&gt;= [&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;serialize&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;] }
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#63a35c;&quot;&gt;coord_2d &lt;&#x2F;span&gt;&lt;span&gt;= { &lt;&#x2F;span&gt;&lt;span style=&quot;color:#63a35c;&quot;&gt;version &lt;&#x2F;span&gt;&lt;span&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;0.3&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#63a35c;&quot;&gt;features &lt;&#x2F;span&gt;&lt;span&gt;= [&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;serialize&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;] }
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#63a35c;&quot;&gt;grid_2d &lt;&#x2F;span&gt;&lt;span&gt;= { &lt;&#x2F;span&gt;&lt;span style=&quot;color:#63a35c;&quot;&gt;version &lt;&#x2F;span&gt;&lt;span&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;0.15&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#63a35c;&quot;&gt;features &lt;&#x2F;span&gt;&lt;span&gt;= [&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;serialize&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;] }
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#63a35c;&quot;&gt;rgb24 &lt;&#x2F;span&gt;&lt;span&gt;= { &lt;&#x2F;span&gt;&lt;span style=&quot;color:#63a35c;&quot;&gt;version &lt;&#x2F;span&gt;&lt;span&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;0.3&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#63a35c;&quot;&gt;features &lt;&#x2F;span&gt;&lt;span&gt;= [&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;serialize&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;] }
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#63a35c;&quot;&gt;direction &lt;&#x2F;span&gt;&lt;span&gt;= { &lt;&#x2F;span&gt;&lt;span style=&quot;color:#63a35c;&quot;&gt;version &lt;&#x2F;span&gt;&lt;span&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;0.18&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#63a35c;&quot;&gt;features &lt;&#x2F;span&gt;&lt;span&gt;= [&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;rand&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;serialize&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;] }
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#63a35c;&quot;&gt;entity_table &lt;&#x2F;span&gt;&lt;span&gt;= { &lt;&#x2F;span&gt;&lt;span style=&quot;color:#63a35c;&quot;&gt;version &lt;&#x2F;span&gt;&lt;span&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;0.2&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#63a35c;&quot;&gt;features &lt;&#x2F;span&gt;&lt;span&gt;= [&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;serialize&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;] }
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#63a35c;&quot;&gt;spatial_table &lt;&#x2F;span&gt;&lt;span&gt;= { &lt;&#x2F;span&gt;&lt;span style=&quot;color:#63a35c;&quot;&gt;version &lt;&#x2F;span&gt;&lt;span&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;0.3&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#63a35c;&quot;&gt;features &lt;&#x2F;span&gt;&lt;span&gt;= [&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;serialize&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;] }
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#63a35c;&quot;&gt;rand &lt;&#x2F;span&gt;&lt;span&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;0.8&amp;quot;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#63a35c;&quot;&gt;rand_isaac &lt;&#x2F;span&gt;&lt;span&gt;= { &lt;&#x2F;span&gt;&lt;span style=&quot;color:#63a35c;&quot;&gt;version &lt;&#x2F;span&gt;&lt;span&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;0.3&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#63a35c;&quot;&gt;features &lt;&#x2F;span&gt;&lt;span&gt;= [&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;serde1&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;] }
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#63a35c;&quot;&gt;shadowcast &lt;&#x2F;span&gt;&lt;span&gt;= { &lt;&#x2F;span&gt;&lt;span style=&quot;color:#63a35c;&quot;&gt;version &lt;&#x2F;span&gt;&lt;span&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;0.8&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#63a35c;&quot;&gt;features &lt;&#x2F;span&gt;&lt;span&gt;= [&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;serialize&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;] }
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#63a35c;&quot;&gt;meap &lt;&#x2F;span&gt;&lt;span&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;0.4&amp;quot;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#63a35c;&quot;&gt;grid_search_cardinal &lt;&#x2F;span&gt;&lt;span&gt;= { &lt;&#x2F;span&gt;&lt;span style=&quot;color:#63a35c;&quot;&gt;version &lt;&#x2F;span&gt;&lt;span&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;0.3&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#63a35c;&quot;&gt;features &lt;&#x2F;span&gt;&lt;span&gt;= [&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;serialize&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;] }
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#63a35c;&quot;&gt;line_2d &lt;&#x2F;span&gt;&lt;span&gt;= { &lt;&#x2F;span&gt;&lt;span style=&quot;color:#63a35c;&quot;&gt;version &lt;&#x2F;span&gt;&lt;span&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;0.5&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#63a35c;&quot;&gt;features &lt;&#x2F;span&gt;&lt;span&gt;= [&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;serialize&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;] }
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#63a35c;&quot;&gt;serde &lt;&#x2F;span&gt;&lt;span&gt;= { &lt;&#x2F;span&gt;&lt;span style=&quot;color:#63a35c;&quot;&gt;version &lt;&#x2F;span&gt;&lt;span&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;1.0&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#63a35c;&quot;&gt;features &lt;&#x2F;span&gt;&lt;span&gt;= [&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;serde_derive&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;] }
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Note the addition of the &lt;code&gt;serde&lt;&#x2F;code&gt; crate.
Most existing crates have had a feature enabled which turn on serialization.&lt;&#x2F;p&gt;
&lt;p&gt;Now in &lt;code&gt;game.rs&lt;&#x2F;code&gt;, import the &lt;code&gt;Serialize&lt;&#x2F;code&gt; and &lt;code&gt;Deserialize&lt;&#x2F;code&gt; traits from &lt;code&gt;serde&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; game.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;use &lt;&#x2F;span&gt;&lt;span&gt;serde::{Deserialize, Serialize};
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;…and derive them for the &lt;code&gt;GameState&lt;&#x2F;code&gt; type:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span&gt;#[derive(Serialize, Deserialize)]
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;pub struct &lt;&#x2F;span&gt;&lt;span&gt;GameState {
&lt;&#x2F;span&gt;&lt;span&gt;    ...
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The derived implementation of (de)serialization will invoke the (de)serialization
methods for each of its fields. Some of its fields won’t &lt;em&gt;have&lt;&#x2F;em&gt; (de)serialization
methods yet, so you’ll see many errors of the form:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;the trait `serde::Deserialize&amp;lt;&amp;#39;_&amp;gt;` is not implemented for &amp;lt;type&amp;gt;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;where &lt;code&gt;&amp;lt;type&amp;gt;&lt;&#x2F;code&gt; is a type defined in the game’s code.&lt;&#x2F;p&gt;
&lt;p&gt;For each type that produces this error, derive the &lt;code&gt;Serialize&lt;&#x2F;code&gt; and &lt;code&gt;Deserialize&lt;&#x2F;code&gt; traits.&lt;&#x2F;p&gt;
&lt;p&gt;Reference implementation branch: &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;gridbugs&#x2F;chargrid-roguelike-tutorial-2020&#x2F;tree&#x2F;part-10.0&quot;&gt;part-10.0&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;h2 id=&quot;main-menu&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#main-menu&quot; aria-label=&quot;Anchor link for: main-menu&quot;&gt;Main Menu&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;Now let’s add a main menu.&lt;&#x2F;p&gt;
&lt;p&gt;Start by defining the main menu entry, view, select, and decorator types, and a function returning an &lt;code&gt;EventRoutine&lt;&#x2F;code&gt;, much as we did
for the inventory menu:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; app.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;#[derive(Clone, Copy, Debug)]
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;enum &lt;&#x2F;span&gt;&lt;span&gt;MainMenuEntry {
&lt;&#x2F;span&gt;&lt;span&gt;    NewGame,
&lt;&#x2F;span&gt;&lt;span&gt;    Resume,
&lt;&#x2F;span&gt;&lt;span&gt;    Quit,
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;main_menu_instance&lt;&#x2F;span&gt;&lt;span&gt;() -&amp;gt; MenuInstanceChooseOrEscape&amp;lt;MainMenuEntry&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;use &lt;&#x2F;span&gt;&lt;span&gt;MainMenuEntry::&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;*&lt;&#x2F;span&gt;&lt;span&gt;;
&lt;&#x2F;span&gt;&lt;span&gt;    MenuInstanceBuilder {
&lt;&#x2F;span&gt;&lt;span&gt;        items: vec![Resume, NewGame, Quit],
&lt;&#x2F;span&gt;&lt;span&gt;        hotkeys: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Some&lt;&#x2F;span&gt;&lt;span&gt;(hashmap![&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;#39;r&amp;#39; &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt; Resume, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;#39;n&amp;#39; &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt; NewGame, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;#39;q&amp;#39; &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt; Quit]),
&lt;&#x2F;span&gt;&lt;span&gt;        selected_index: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;    .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;build&lt;&#x2F;span&gt;&lt;span&gt;()
&lt;&#x2F;span&gt;&lt;span&gt;    .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;unwrap&lt;&#x2F;span&gt;&lt;span&gt;()
&lt;&#x2F;span&gt;&lt;span&gt;    .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;into_choose_or_escape&lt;&#x2F;span&gt;&lt;span&gt;()
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;#[derive(Default)]
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;struct &lt;&#x2F;span&gt;&lt;span&gt;MainMenuView {
&lt;&#x2F;span&gt;&lt;span&gt;    mouse_tracker: MenuInstanceMouseTracker,
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;impl &lt;&#x2F;span&gt;&lt;span&gt;MenuIndexFromScreenCoord &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;for &lt;&#x2F;span&gt;&lt;span&gt;MainMenuView {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;menu_index_from_screen_coord&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;self, len: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;usize&lt;&#x2F;span&gt;&lt;span&gt;, coord: Coord) -&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Option&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;usize&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;        self.mouse_tracker.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;menu_index_from_screen_coord&lt;&#x2F;span&gt;&lt;span&gt;(len, coord)
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;impl&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;#39;a&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt; View&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&amp;#39;a&lt;&#x2F;span&gt;&lt;span&gt; AppData&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;for &lt;&#x2F;span&gt;&lt;span&gt;MainMenuView {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;view&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;F: Frame, C: ColModify&amp;gt;(
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut &lt;&#x2F;span&gt;&lt;span&gt;self,
&lt;&#x2F;span&gt;&lt;span&gt;        data: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&amp;#39;a&lt;&#x2F;span&gt;&lt;span&gt; AppData,
&lt;&#x2F;span&gt;&lt;span&gt;        context: ViewContext&amp;lt;C&amp;gt;,
&lt;&#x2F;span&gt;&lt;span&gt;        frame: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut&lt;&#x2F;span&gt;&lt;span&gt; F,
&lt;&#x2F;span&gt;&lt;span&gt;    ) {
&lt;&#x2F;span&gt;&lt;span&gt;        self.mouse_tracker.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;new_frame&lt;&#x2F;span&gt;&lt;span&gt;(context.offset);
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;for &lt;&#x2F;span&gt;&lt;span&gt;(i, &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;entry, maybe_selected) &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;in&lt;&#x2F;span&gt;&lt;span&gt; data.main_menu.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;menu_instance&lt;&#x2F;span&gt;&lt;span&gt;().&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;enumerate&lt;&#x2F;span&gt;&lt;span&gt;() {
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;let &lt;&#x2F;span&gt;&lt;span&gt;(prefix, style) &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= if&lt;&#x2F;span&gt;&lt;span&gt; maybe_selected.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;is_some&lt;&#x2F;span&gt;&lt;span&gt;() {
&lt;&#x2F;span&gt;&lt;span&gt;                (
&lt;&#x2F;span&gt;&lt;span&gt;                    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;&amp;gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;                    Style::new()
&lt;&#x2F;span&gt;&lt;span&gt;                        .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;with_foreground&lt;&#x2F;span&gt;&lt;span&gt;(Rgb24::new_grey(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;255&lt;&#x2F;span&gt;&lt;span&gt;))
&lt;&#x2F;span&gt;&lt;span&gt;                        .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;with_bold&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;true&lt;&#x2F;span&gt;&lt;span&gt;),
&lt;&#x2F;span&gt;&lt;span&gt;                )
&lt;&#x2F;span&gt;&lt;span&gt;            } &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;else &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;                (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot; &amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;, Style::new().&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;with_foreground&lt;&#x2F;span&gt;&lt;span&gt;(Rgb24::new_grey(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;187&lt;&#x2F;span&gt;&lt;span&gt;)))
&lt;&#x2F;span&gt;&lt;span&gt;            };
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; text &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= match&lt;&#x2F;span&gt;&lt;span&gt; entry {
&lt;&#x2F;span&gt;&lt;span&gt;                MainMenuEntry::Resume &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;(r) Resume&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;                MainMenuEntry::NewGame &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;(n) New Game&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;                MainMenuEntry::Quit &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;(q) Quit&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;            };
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; size &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span&gt;StringViewSingleLine::new(style).&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;view_size&lt;&#x2F;span&gt;&lt;span&gt;(
&lt;&#x2F;span&gt;&lt;span&gt;                format!(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;{} {}&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;, prefix, text),
&lt;&#x2F;span&gt;&lt;span&gt;                context.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;add_offset&lt;&#x2F;span&gt;&lt;span&gt;(Coord::new(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt;, i &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;as i32&lt;&#x2F;span&gt;&lt;span&gt;)),
&lt;&#x2F;span&gt;&lt;span&gt;                frame,
&lt;&#x2F;span&gt;&lt;span&gt;            );
&lt;&#x2F;span&gt;&lt;span&gt;            self.mouse_tracker.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;on_entry_view_size&lt;&#x2F;span&gt;&lt;span&gt;(size);
&lt;&#x2F;span&gt;&lt;span&gt;        }
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;struct &lt;&#x2F;span&gt;&lt;span&gt;MainMenuSelect;
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;impl &lt;&#x2F;span&gt;&lt;span&gt;ChooseSelector &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;for &lt;&#x2F;span&gt;&lt;span&gt;MainMenuSelect {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;type &lt;&#x2F;span&gt;&lt;span&gt;ChooseOutput &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span&gt;MenuInstanceChooseOrEscape&amp;lt;MainMenuEntry&amp;gt;;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;choose_mut&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;#39;a&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;self, input: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&amp;#39;a mut Self::&lt;&#x2F;span&gt;&lt;span&gt;DataInput) -&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&amp;#39;a mut Self::&lt;&#x2F;span&gt;&lt;span&gt;ChooseOutput {
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut&lt;&#x2F;span&gt;&lt;span&gt; input.main_menu
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;impl &lt;&#x2F;span&gt;&lt;span&gt;DataSelector &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;for &lt;&#x2F;span&gt;&lt;span&gt;MainMenuSelect {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;type &lt;&#x2F;span&gt;&lt;span&gt;DataInput &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; AppData;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;type &lt;&#x2F;span&gt;&lt;span&gt;DataOutput &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; AppData;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;data&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;#39;a&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;self, input: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&amp;#39;a Self::&lt;&#x2F;span&gt;&lt;span&gt;DataInput) -&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&amp;#39;a Self::&lt;&#x2F;span&gt;&lt;span&gt;DataOutput {
&lt;&#x2F;span&gt;&lt;span&gt;        input
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;data_mut&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;#39;a&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;self, input: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&amp;#39;a mut Self::&lt;&#x2F;span&gt;&lt;span&gt;DataInput) -&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&amp;#39;a mut Self::&lt;&#x2F;span&gt;&lt;span&gt;DataOutput {
&lt;&#x2F;span&gt;&lt;span&gt;        input
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;impl &lt;&#x2F;span&gt;&lt;span&gt;ViewSelector &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;for &lt;&#x2F;span&gt;&lt;span&gt;MainMenuSelect {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;type &lt;&#x2F;span&gt;&lt;span&gt;ViewInput &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; AppView;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;type &lt;&#x2F;span&gt;&lt;span&gt;ViewOutput &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; MainMenuView;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;view&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;#39;a&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;self, input: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&amp;#39;a Self::&lt;&#x2F;span&gt;&lt;span&gt;ViewInput) -&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&amp;#39;a Self::&lt;&#x2F;span&gt;&lt;span&gt;ViewOutput {
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;input.main_menu_view
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;view_mut&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;#39;a&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;self, input: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&amp;#39;a mut Self::&lt;&#x2F;span&gt;&lt;span&gt;ViewInput) -&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&amp;#39;a mut Self::&lt;&#x2F;span&gt;&lt;span&gt;ViewOutput {
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut&lt;&#x2F;span&gt;&lt;span&gt; input.main_menu_view
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;struct &lt;&#x2F;span&gt;&lt;span&gt;MainMenuDecorate;
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;impl &lt;&#x2F;span&gt;&lt;span&gt;Decorate &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;for &lt;&#x2F;span&gt;&lt;span&gt;MainMenuDecorate {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;type &lt;&#x2F;span&gt;&lt;span&gt;View &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; AppView;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;type &lt;&#x2F;span&gt;&lt;span&gt;Data &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; AppData;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;view&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;E, F, C&amp;gt;(
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;self,
&lt;&#x2F;span&gt;&lt;span&gt;        data: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;Self::&lt;&#x2F;span&gt;&lt;span&gt;Data,
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;mut &lt;&#x2F;span&gt;&lt;span&gt;event_routine_view: EventRoutineView&amp;lt;E&amp;gt;,
&lt;&#x2F;span&gt;&lt;span&gt;        context: ViewContext&amp;lt;C&amp;gt;,
&lt;&#x2F;span&gt;&lt;span&gt;        frame: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut&lt;&#x2F;span&gt;&lt;span&gt; F,
&lt;&#x2F;span&gt;&lt;span&gt;    ) &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;where
&lt;&#x2F;span&gt;&lt;span&gt;        E: EventRoutine&amp;lt;Data = &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;Self::&lt;&#x2F;span&gt;&lt;span&gt;Data, View = &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;Self::&lt;&#x2F;span&gt;&lt;span&gt;View&amp;gt;,
&lt;&#x2F;span&gt;&lt;span&gt;        F: Frame,
&lt;&#x2F;span&gt;&lt;span&gt;        C: ColModify,
&lt;&#x2F;span&gt;&lt;span&gt;    {
&lt;&#x2F;span&gt;&lt;span&gt;        BoundView {
&lt;&#x2F;span&gt;&lt;span&gt;            size: data.game_state.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;size&lt;&#x2F;span&gt;&lt;span&gt;(),
&lt;&#x2F;span&gt;&lt;span&gt;            view: AlignView {
&lt;&#x2F;span&gt;&lt;span&gt;                alignment: Alignment::centre(),
&lt;&#x2F;span&gt;&lt;span&gt;                view: FillBackgroundView {
&lt;&#x2F;span&gt;&lt;span&gt;                    rgb24: Rgb24::new_grey(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt;),
&lt;&#x2F;span&gt;&lt;span&gt;                    view: BorderView {
&lt;&#x2F;span&gt;&lt;span&gt;                        style: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;BorderStyle {
&lt;&#x2F;span&gt;&lt;span&gt;                            title: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;None&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;                            title_style: Style::new().&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;with_foreground&lt;&#x2F;span&gt;&lt;span&gt;(Rgb24::new_grey(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;255&lt;&#x2F;span&gt;&lt;span&gt;)),
&lt;&#x2F;span&gt;&lt;span&gt;                            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;..&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Default&lt;&#x2F;span&gt;&lt;span&gt;::default()
&lt;&#x2F;span&gt;&lt;span&gt;                        },
&lt;&#x2F;span&gt;&lt;span&gt;                        view: MinSizeView {
&lt;&#x2F;span&gt;&lt;span&gt;                            size: Size::new(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;12&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt;),
&lt;&#x2F;span&gt;&lt;span&gt;                            view: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut&lt;&#x2F;span&gt;&lt;span&gt; event_routine_view,
&lt;&#x2F;span&gt;&lt;span&gt;                        },
&lt;&#x2F;span&gt;&lt;span&gt;                    },
&lt;&#x2F;span&gt;&lt;span&gt;                },
&lt;&#x2F;span&gt;&lt;span&gt;            },
&lt;&#x2F;span&gt;&lt;span&gt;        }
&lt;&#x2F;span&gt;&lt;span&gt;        .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;view&lt;&#x2F;span&gt;&lt;span&gt;(data, context.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;add_depth&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;10&lt;&#x2F;span&gt;&lt;span&gt;), frame);
&lt;&#x2F;span&gt;&lt;span&gt;        event_routine_view.view.game_view.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;view&lt;&#x2F;span&gt;&lt;span&gt;(
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;data.game_state,
&lt;&#x2F;span&gt;&lt;span&gt;            context.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;compose_col_modify&lt;&#x2F;span&gt;&lt;span&gt;(ColModifyMap(|c: Rgb24| c.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;saturating_scalar_mul_div&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;2&lt;&#x2F;span&gt;&lt;span&gt;))),
&lt;&#x2F;span&gt;&lt;span&gt;            frame,
&lt;&#x2F;span&gt;&lt;span&gt;        );
&lt;&#x2F;span&gt;&lt;span&gt;        event_routine_view
&lt;&#x2F;span&gt;&lt;span&gt;            .view
&lt;&#x2F;span&gt;&lt;span&gt;            .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;render_ui&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;None&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;data, context, frame);
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;main_menu&lt;&#x2F;span&gt;&lt;span&gt;() -&amp;gt; impl EventRoutine&amp;lt;
&lt;&#x2F;span&gt;&lt;span&gt;    Return = &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Result&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;MainMenuEntry, menu::Escape&amp;gt;,
&lt;&#x2F;span&gt;&lt;span&gt;    Data = AppData,
&lt;&#x2F;span&gt;&lt;span&gt;    View = AppView,
&lt;&#x2F;span&gt;&lt;span&gt;    Event = CommonEvent,
&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;    MenuInstanceRoutine::new(MainMenuSelect)
&lt;&#x2F;span&gt;&lt;span&gt;        .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;convert_input_to_common_event&lt;&#x2F;span&gt;&lt;span&gt;()
&lt;&#x2F;span&gt;&lt;span&gt;        .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;decorated&lt;&#x2F;span&gt;&lt;span&gt;(MainMenuDecorate)
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Note the &lt;code&gt;hashmap!&lt;&#x2F;code&gt; macro used to specify hotkeys for the main menu.
This is from a crate called &lt;code&gt;maplit&lt;&#x2F;code&gt;, which needs to be imported.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;toml&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-toml &quot;&gt;&lt;code class=&quot;language-toml&quot; data-lang=&quot;toml&quot;&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;# Cargo.toml
&lt;&#x2F;span&gt;&lt;span style=&quot;background-color:#f5f5f5;font-weight:bold;color:#b52a1d;&quot;&gt;...&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;[dependencies]
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#63a35c;&quot;&gt;maplit &lt;&#x2F;span&gt;&lt;span&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;1.0&amp;quot;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; app.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;use &lt;&#x2F;span&gt;&lt;span&gt;maplit::hashmap;
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Add the relevant main menu types to &lt;code&gt;AppData&lt;&#x2F;code&gt; and &lt;code&gt;AppView&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;struct &lt;&#x2F;span&gt;&lt;span&gt;AppData {
&lt;&#x2F;span&gt;&lt;span&gt;    ...
&lt;&#x2F;span&gt;&lt;span&gt;    main_menu: MenuInstanceChooseOrEscape&amp;lt;MainMenuEntry&amp;gt;,
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;impl &lt;&#x2F;span&gt;&lt;span&gt;Data {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;new&lt;&#x2F;span&gt;&lt;span&gt;(screen_size: Size, rng_seed: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;u64&lt;&#x2F;span&gt;&lt;span&gt;, visibility_algorithm: VisibilityAlgorithm) -&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;Self &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;Self &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;            main_menu: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;main_menu_instance&lt;&#x2F;span&gt;&lt;span&gt;(),
&lt;&#x2F;span&gt;&lt;span&gt;        }
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;struct &lt;&#x2F;span&gt;&lt;span&gt;AppView {
&lt;&#x2F;span&gt;&lt;span&gt;    ...
&lt;&#x2F;span&gt;&lt;span&gt;    main_menu_view: MainMenuView,
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;impl &lt;&#x2F;span&gt;&lt;span&gt;AppView {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;new&lt;&#x2F;span&gt;&lt;span&gt;(screen_size: Size) -&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;Self &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;Self &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;            main_menu_view: MainMenuView::default(),
&lt;&#x2F;span&gt;&lt;span&gt;        }
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;At the moment, when the escape key is pressed, the game exits. Let’s change it so that the
menu opens instead. There’s no longer a need for the &lt;code&gt;GameReturn::Exit&lt;&#x2F;code&gt; variant, so remove it.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;enum &lt;&#x2F;span&gt;&lt;span&gt;GameReturn {
&lt;&#x2F;span&gt;&lt;span&gt;    Menu,
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;impl &lt;&#x2F;span&gt;&lt;span&gt;AppData {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;handle_input&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut &lt;&#x2F;span&gt;&lt;span&gt;self, input: Input) -&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Option&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;GameReturn&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;match&lt;&#x2F;span&gt;&lt;span&gt; input {
&lt;&#x2F;span&gt;&lt;span&gt;            Input::Keyboard(key) &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;                &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;match&lt;&#x2F;span&gt;&lt;span&gt; key {
&lt;&#x2F;span&gt;&lt;span&gt;                    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;                    keys::&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;ESCAPE &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; return &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Some&lt;&#x2F;span&gt;&lt;span&gt;(GameReturn::Menu),
&lt;&#x2F;span&gt;&lt;span&gt;                    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;                }
&lt;&#x2F;span&gt;&lt;span&gt;                &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;            }
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;        }
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Handle the &lt;code&gt;GameReturn::Menu&lt;&#x2F;code&gt; value in &lt;code&gt;game_loop&lt;&#x2F;code&gt;. Have it run the &lt;code&gt;main_menu()&lt;&#x2F;code&gt; event routine
and handle the choice from that menu.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;game_loop&lt;&#x2F;span&gt;&lt;span&gt;() -&amp;gt; impl EventRoutine&amp;lt;Return = (), Data = AppData, View = AppView, Event = CommonEvent&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;    make_either!(Ei &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; A &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;|&lt;&#x2F;span&gt;&lt;span&gt; B &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;|&lt;&#x2F;span&gt;&lt;span&gt; C &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;|&lt;&#x2F;span&gt;&lt;span&gt; D &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;|&lt;&#x2F;span&gt;&lt;span&gt; E);
&lt;&#x2F;span&gt;&lt;span&gt;    Loop::new(|| {
&lt;&#x2F;span&gt;&lt;span&gt;        GameEventRoutine.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;and_then&lt;&#x2F;span&gt;&lt;span&gt;(|game_return| &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;match&lt;&#x2F;span&gt;&lt;span&gt; game_return {
&lt;&#x2F;span&gt;&lt;span&gt;            GameReturn::Menu &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span&gt;Ei::A(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;main_menu&lt;&#x2F;span&gt;&lt;span&gt;().&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;and_then&lt;&#x2F;span&gt;&lt;span&gt;(|choice| {
&lt;&#x2F;span&gt;&lt;span&gt;                make_either!(Ei &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; A &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;|&lt;&#x2F;span&gt;&lt;span&gt; B);
&lt;&#x2F;span&gt;&lt;span&gt;                &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;match&lt;&#x2F;span&gt;&lt;span&gt; choice {
&lt;&#x2F;span&gt;&lt;span&gt;                    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Err&lt;&#x2F;span&gt;&lt;span&gt;(menu::Escape) &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span&gt;Ei::A(Value::new(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;None&lt;&#x2F;span&gt;&lt;span&gt;)),
&lt;&#x2F;span&gt;&lt;span&gt;                    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Ok&lt;&#x2F;span&gt;&lt;span&gt;(MainMenuEntry::Resume) &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span&gt;Ei::A(Value::new(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;None&lt;&#x2F;span&gt;&lt;span&gt;)),
&lt;&#x2F;span&gt;&lt;span&gt;                    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Ok&lt;&#x2F;span&gt;&lt;span&gt;(MainMenuEntry::Quit) &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span&gt;Ei::A(Value::new(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Some&lt;&#x2F;span&gt;&lt;span&gt;(()))),
&lt;&#x2F;span&gt;&lt;span&gt;                    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Ok&lt;&#x2F;span&gt;&lt;span&gt;(MainMenuEntry::NewGame) &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;                        Ei::B(SideEffect::new_with_view(|data: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut&lt;&#x2F;span&gt;&lt;span&gt; AppData, _: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;_| {
&lt;&#x2F;span&gt;&lt;span&gt;                            data.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;new_game&lt;&#x2F;span&gt;&lt;span&gt;();
&lt;&#x2F;span&gt;&lt;span&gt;                            &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;None
&lt;&#x2F;span&gt;&lt;span&gt;                        }))
&lt;&#x2F;span&gt;&lt;span&gt;                    }
&lt;&#x2F;span&gt;&lt;span&gt;                }
&lt;&#x2F;span&gt;&lt;span&gt;            })),
&lt;&#x2F;span&gt;&lt;span&gt;            GameReturn::GameOver &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span&gt;Ei::B(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;game_over&lt;&#x2F;span&gt;&lt;span&gt;().&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;map&lt;&#x2F;span&gt;&lt;span&gt;(|()| &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Some&lt;&#x2F;span&gt;&lt;span&gt;(()))),
&lt;&#x2F;span&gt;&lt;span&gt;            GameReturn::UseItem &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span&gt;Ei::C(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;use_item&lt;&#x2F;span&gt;&lt;span&gt;().&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;map&lt;&#x2F;span&gt;&lt;span&gt;(|_| &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;None&lt;&#x2F;span&gt;&lt;span&gt;)),
&lt;&#x2F;span&gt;&lt;span&gt;            GameReturn::DropItem &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span&gt;Ei::D(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;drop_item&lt;&#x2F;span&gt;&lt;span&gt;().&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;map&lt;&#x2F;span&gt;&lt;span&gt;(|_| &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;None&lt;&#x2F;span&gt;&lt;span&gt;)),
&lt;&#x2F;span&gt;&lt;span&gt;            GameReturn::Examine &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span&gt;Ei::E(TargetEventRoutine { name: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;EXAMINE&amp;quot; &lt;&#x2F;span&gt;&lt;span&gt;}.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;map&lt;&#x2F;span&gt;&lt;span&gt;(|_| &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;None&lt;&#x2F;span&gt;&lt;span&gt;)),
&lt;&#x2F;span&gt;&lt;span&gt;        })
&lt;&#x2F;span&gt;&lt;span&gt;    })
&lt;&#x2F;span&gt;&lt;span&gt;    .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;return_on_exit&lt;&#x2F;span&gt;&lt;span&gt;(|_| ())
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;In the &lt;code&gt;NewGame&lt;&#x2F;code&gt; case, we’re calling a &lt;code&gt;.new_game()&lt;&#x2F;code&gt; method of &lt;code&gt;AppData&lt;&#x2F;code&gt; which we’ve yet to implement.
Implement this now. This will require adding some fields to &lt;code&gt;AppData&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;struct &lt;&#x2F;span&gt;&lt;span&gt;AppData {
&lt;&#x2F;span&gt;&lt;span&gt;    ...
&lt;&#x2F;span&gt;&lt;span&gt;    game_area_size: Size,
&lt;&#x2F;span&gt;&lt;span&gt;    rng_seed: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;u64&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;impl &lt;&#x2F;span&gt;&lt;span&gt;AppData {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;new&lt;&#x2F;span&gt;&lt;span&gt;(screen_size: Size, rng_seed: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;u64&lt;&#x2F;span&gt;&lt;span&gt;, visibility_algorithm: VisibilityAlgorithm) -&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;Self &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; game_area_size &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; screen_size.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;set_height&lt;&#x2F;span&gt;&lt;span&gt;(screen_size.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;height&lt;&#x2F;span&gt;&lt;span&gt;() &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;- &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;UI_NUM_ROWS&lt;&#x2F;span&gt;&lt;span&gt;);
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;Self &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;            game_area_size,
&lt;&#x2F;span&gt;&lt;span&gt;            rng_seed,
&lt;&#x2F;span&gt;&lt;span&gt;        }
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;new_game&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut &lt;&#x2F;span&gt;&lt;span&gt;self) {
&lt;&#x2F;span&gt;&lt;span&gt;        self.rng_seed &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span&gt;self.rng_seed.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;wrapping_add&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;);
&lt;&#x2F;span&gt;&lt;span&gt;        self.game_state &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span&gt;GameState::new(
&lt;&#x2F;span&gt;&lt;span&gt;            self.game_area_size,
&lt;&#x2F;span&gt;&lt;span&gt;            self.rng_seed,
&lt;&#x2F;span&gt;&lt;span&gt;            self.visibility_algorithm,
&lt;&#x2F;span&gt;&lt;span&gt;        );
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The rng seed is incremented so each time a new game is started, its random number generator is
in a different state, and the level will be generated differently.
Since the rng seed is changing mid-game, rather than being set once at startup, move
the code that prints the rng seed from &lt;code&gt;main&lt;&#x2F;code&gt; to &lt;code&gt;GameState::new&lt;&#x2F;code&gt;, so if you observe
an error after hitting &lt;code&gt;New Game&lt;&#x2F;code&gt; several times, it’s still possible to easily reproduce it.&lt;&#x2F;p&gt;
&lt;p&gt;Now that we have the ability to start a new game, change &lt;code&gt;game_loop&lt;&#x2F;code&gt; again so that when the player dies,
a new game is started.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;game_loop&lt;&#x2F;span&gt;&lt;span&gt;() -&amp;gt; impl EventRoutine&amp;lt;Return = (), Data = AppData, View = AppView, Event = CommonEvent&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;    make_either!(Ei &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; A &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;|&lt;&#x2F;span&gt;&lt;span&gt; B &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;|&lt;&#x2F;span&gt;&lt;span&gt; C &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;|&lt;&#x2F;span&gt;&lt;span&gt; D &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;|&lt;&#x2F;span&gt;&lt;span&gt; E);
&lt;&#x2F;span&gt;&lt;span&gt;    Loop::new(|| {
&lt;&#x2F;span&gt;&lt;span&gt;        GameEventRoutine.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;and_then&lt;&#x2F;span&gt;&lt;span&gt;(|game_return| &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;match&lt;&#x2F;span&gt;&lt;span&gt; game_return {
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;            GameReturn::GameOver &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span&gt;Ei::B(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;game_over&lt;&#x2F;span&gt;&lt;span&gt;().&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;and_then&lt;&#x2F;span&gt;&lt;span&gt;(|()| {
&lt;&#x2F;span&gt;&lt;span&gt;                SideEffect::new_with_view(|data: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut&lt;&#x2F;span&gt;&lt;span&gt; AppData, _: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;_| {
&lt;&#x2F;span&gt;&lt;span&gt;                    data.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;new_game&lt;&#x2F;span&gt;&lt;span&gt;();
&lt;&#x2F;span&gt;&lt;span&gt;                    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;None
&lt;&#x2F;span&gt;&lt;span&gt;                })
&lt;&#x2F;span&gt;&lt;span&gt;            })),
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;        })
&lt;&#x2F;span&gt;&lt;span&gt;    })
&lt;&#x2F;span&gt;&lt;span&gt;    .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;return_on_exit&lt;&#x2F;span&gt;&lt;span&gt;(|_| ())
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;roguelike-tutorial-2020-part-10&#x2F;menu1.png&quot; alt=&quot;menu1.png&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Reference implementation branch: &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;gridbugs&#x2F;chargrid-roguelike-tutorial-2020&#x2F;tree&#x2F;part-10.1&quot;&gt;part-10.1&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;h2 id=&quot;saving&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#saving&quot; aria-label=&quot;Anchor link for: saving&quot;&gt;Saving&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;Add the &lt;code&gt;general_storage_file&lt;&#x2F;code&gt; crate which will help with storing and retrieving serialized state in a file.
The goal of this crate is to present an abstract view of persistent data, backed by files in a directory.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;toml&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-toml &quot;&gt;&lt;code class=&quot;language-toml&quot; data-lang=&quot;toml&quot;&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;# Cargo.toml
&lt;&#x2F;span&gt;&lt;span style=&quot;background-color:#f5f5f5;font-weight:bold;color:#b52a1d;&quot;&gt;...&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;[dependencies]
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#63a35c;&quot;&gt;general_storage_file &lt;&#x2F;span&gt;&lt;span&gt;= { &lt;&#x2F;span&gt;&lt;span style=&quot;color:#63a35c;&quot;&gt;version &lt;&#x2F;span&gt;&lt;span&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;0.1&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#63a35c;&quot;&gt;features &lt;&#x2F;span&gt;&lt;span&gt;= [&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;json&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;compress&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;] }
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Note the &lt;code&gt;json&lt;&#x2F;code&gt; and &lt;code&gt;compress&lt;&#x2F;code&gt; features. This crate lets you choose between a number of different data serialization
formats, but all are disabled by default and require explicit features to enable. This is because each format
depends on additional crates. We reduce the transitive dependencies of our game by only adding storage
formats which we need.&lt;&#x2F;p&gt;
&lt;p&gt;Now in &lt;code&gt;app.rs&lt;&#x2F;code&gt;, start using the crate, and define some constants that will configure how we use the crate.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; app.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;use &lt;&#x2F;span&gt;&lt;span&gt;general_storage_file::{format, FileStorage, IfDirectoryMissing, Storage};
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;const &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;SAVE_DIR&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;str = &lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;save&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;;
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;const &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;SAVE_FILE&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;str = &lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;save&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;;
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;const &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;SAVE_FORMAT&lt;&#x2F;span&gt;&lt;span&gt;: format::Compress&amp;lt;format::Json&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span&gt;format::Compress(format::Json);
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;code&gt;SAVE_DIR&lt;&#x2F;code&gt; is the directory in which the save file will be placed. &lt;code&gt;SAVE_FILE&lt;&#x2F;code&gt; is the name of the file
which will contain the save game. &lt;code&gt;SAVE_FORMAT&lt;&#x2F;code&gt; defines how the game’s state will be serialized.
&lt;code&gt;format::Compress(format::Json)&lt;&#x2F;code&gt; means create a json string representing the game’s state, then
compress that (with gzip). An alternative format, &lt;code&gt;format::Bincode&lt;&#x2F;code&gt;
is available with the &lt;code&gt;bincode&lt;&#x2F;code&gt; feature flag, which serializes with the &lt;a href=&quot;https:&#x2F;&#x2F;crates.io&#x2F;crates&#x2F;bincode&quot;&gt;bincode&lt;&#x2F;a&gt;
crate. It’s not used here, as it causes programs to crash if the type definitions change between
serializing and deserializing data (which &lt;em&gt;will&lt;&#x2F;em&gt; happen here as we’re constantly adding to this game!).
In contrast, the json serializer just returns an error in this situation. Switch to bincode once the
game is finished.&lt;&#x2F;p&gt;
&lt;p&gt;Replace &lt;code&gt;MainMenu::Quit&lt;&#x2F;code&gt; with &lt;code&gt;MainMenu::SaveAndQuit&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;enum &lt;&#x2F;span&gt;&lt;span&gt;MainMenuEntry {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;    SaveAndQuit,
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;main_menu_instance&lt;&#x2F;span&gt;&lt;span&gt;() -&amp;gt; MenuInstanceChooseOrEscape&amp;lt;MainMenuEntry&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;use &lt;&#x2F;span&gt;&lt;span&gt;MainMenuEntry::&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;*&lt;&#x2F;span&gt;&lt;span&gt;;
&lt;&#x2F;span&gt;&lt;span&gt;    MenuInstanceBuilder {
&lt;&#x2F;span&gt;&lt;span&gt;        items: vec![Resume, NewGame, SaveAndQuit],
&lt;&#x2F;span&gt;&lt;span&gt;        hotkeys: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Some&lt;&#x2F;span&gt;&lt;span&gt;(hashmap![&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;#39;r&amp;#39; &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt; Resume, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;#39;n&amp;#39; &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt; NewGame, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;#39;q&amp;#39; &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt; SaveAndQuit]),
&lt;&#x2F;span&gt;&lt;span&gt;        selected_index: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;    .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;build&lt;&#x2F;span&gt;&lt;span&gt;()
&lt;&#x2F;span&gt;&lt;span&gt;    .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;unwrap&lt;&#x2F;span&gt;&lt;span&gt;()
&lt;&#x2F;span&gt;&lt;span&gt;    .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;into_choose_or_escape&lt;&#x2F;span&gt;&lt;span&gt;()
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;impl&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;#39;a&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt; View&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&amp;#39;a&lt;&#x2F;span&gt;&lt;span&gt; AppData&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;for &lt;&#x2F;span&gt;&lt;span&gt;MainMenuView {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;view&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;F: Frame, C: ColModify&amp;gt;(
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut &lt;&#x2F;span&gt;&lt;span&gt;self,
&lt;&#x2F;span&gt;&lt;span&gt;        data: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&amp;#39;a&lt;&#x2F;span&gt;&lt;span&gt; AppData,
&lt;&#x2F;span&gt;&lt;span&gt;        context: ViewContext&amp;lt;C&amp;gt;,
&lt;&#x2F;span&gt;&lt;span&gt;        frame: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut&lt;&#x2F;span&gt;&lt;span&gt; F,
&lt;&#x2F;span&gt;&lt;span&gt;    ) {
&lt;&#x2F;span&gt;&lt;span&gt;        self.mouse_tracker.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;new_frame&lt;&#x2F;span&gt;&lt;span&gt;(context.offset);
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;for &lt;&#x2F;span&gt;&lt;span&gt;(i, &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;entry, maybe_selected) &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;in&lt;&#x2F;span&gt;&lt;span&gt; data.main_menu.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;menu_instance&lt;&#x2F;span&gt;&lt;span&gt;().&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;enumerate&lt;&#x2F;span&gt;&lt;span&gt;() {
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; text &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= match&lt;&#x2F;span&gt;&lt;span&gt; entry {
&lt;&#x2F;span&gt;&lt;span&gt;                MainMenuEntry::Resume &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;(r) Resume&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;                MainMenuEntry::NewGame &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;(n) New Game&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;                MainMenuEntry::SaveAndQuit &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;(q) Save and Quit&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;            };
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;        }
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;game_loop&lt;&#x2F;span&gt;&lt;span&gt;() -&amp;gt; impl EventRoutine&amp;lt;Return = (), Data = AppData, View = AppView, Event = CommonEvent&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;    make_either!(Ei &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; A &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;|&lt;&#x2F;span&gt;&lt;span&gt; B &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;|&lt;&#x2F;span&gt;&lt;span&gt; C &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;|&lt;&#x2F;span&gt;&lt;span&gt; D &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;|&lt;&#x2F;span&gt;&lt;span&gt; E);
&lt;&#x2F;span&gt;&lt;span&gt;    Loop::new(|| {
&lt;&#x2F;span&gt;&lt;span&gt;        GameEventRoutine.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;and_then&lt;&#x2F;span&gt;&lt;span&gt;(|game_return| &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;match&lt;&#x2F;span&gt;&lt;span&gt; game_return {
&lt;&#x2F;span&gt;&lt;span&gt;            GameReturn::Menu &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span&gt;Ei::A(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;main_menu&lt;&#x2F;span&gt;&lt;span&gt;().&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;and_then&lt;&#x2F;span&gt;&lt;span&gt;(|choice| {
&lt;&#x2F;span&gt;&lt;span&gt;                make_either!(Ei &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; A &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;|&lt;&#x2F;span&gt;&lt;span&gt; B &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;|&lt;&#x2F;span&gt;&lt;span&gt; C);
&lt;&#x2F;span&gt;&lt;span&gt;                &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;match&lt;&#x2F;span&gt;&lt;span&gt; choice {
&lt;&#x2F;span&gt;&lt;span&gt;                    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;                    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Ok&lt;&#x2F;span&gt;&lt;span&gt;(MainMenuEntry::SaveAndQuit) &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;                        Ei::C(SideEffect::new_with_view(|data: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut&lt;&#x2F;span&gt;&lt;span&gt; AppData, _: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;_| {
&lt;&#x2F;span&gt;&lt;span&gt;                            &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Some&lt;&#x2F;span&gt;&lt;span&gt;(())
&lt;&#x2F;span&gt;&lt;span&gt;                        }))
&lt;&#x2F;span&gt;&lt;span&gt;                    }
&lt;&#x2F;span&gt;&lt;span&gt;                    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;                }
&lt;&#x2F;span&gt;&lt;span&gt;            })),
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;        })
&lt;&#x2F;span&gt;&lt;span&gt;    })
&lt;&#x2F;span&gt;&lt;span&gt;    .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;return_on_exit&lt;&#x2F;span&gt;&lt;span&gt;(|data| data.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;save_game&lt;&#x2F;span&gt;&lt;span&gt;())
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Implement a method for saving the game state to a file.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;impl &lt;&#x2F;span&gt;&lt;span&gt;AppData {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;save_game&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;self) {
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;let mut&lt;&#x2F;span&gt;&lt;span&gt; file_storage &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= match &lt;&#x2F;span&gt;&lt;span&gt;FileStorage::next_to_exe(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;SAVE_DIR&lt;&#x2F;span&gt;&lt;span&gt;, IfDirectoryMissing::Create)
&lt;&#x2F;span&gt;&lt;span&gt;        {
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Ok&lt;&#x2F;span&gt;&lt;span&gt;(file_storage) &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt; file_storage,
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Err&lt;&#x2F;span&gt;&lt;span&gt;(error) &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;                eprintln!(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;Failed to save game: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;{:?}&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;, error);
&lt;&#x2F;span&gt;&lt;span&gt;                &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;return&lt;&#x2F;span&gt;&lt;span&gt;;
&lt;&#x2F;span&gt;&lt;span&gt;            }
&lt;&#x2F;span&gt;&lt;span&gt;        };
&lt;&#x2F;span&gt;&lt;span&gt;        println!(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;Saving to &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;{:?}&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;, file_storage.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;full_path&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;SAVE_FILE&lt;&#x2F;span&gt;&lt;span&gt;));
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;match&lt;&#x2F;span&gt;&lt;span&gt; file_storage.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;store&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;SAVE_FILE&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;self.game_state, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;SAVE_FORMAT&lt;&#x2F;span&gt;&lt;span&gt;) {
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Ok&lt;&#x2F;span&gt;&lt;span&gt;(()) &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span&gt;(),
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Err&lt;&#x2F;span&gt;&lt;span&gt;(error) &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;                eprintln!(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;Failed to save game: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;{:?}&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;, error);
&lt;&#x2F;span&gt;&lt;span&gt;                &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;return&lt;&#x2F;span&gt;&lt;span&gt;;
&lt;&#x2F;span&gt;&lt;span&gt;            }
&lt;&#x2F;span&gt;&lt;span&gt;        }
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;It creates a directory called “save” next to the game’s executable, and serializes the game’s state
into a file in this directory, also called “save”.&lt;&#x2F;p&gt;
&lt;p&gt;Now call this method from &lt;code&gt;game_loop&lt;&#x2F;code&gt;, both when &lt;code&gt;SaveAndQuit&lt;&#x2F;code&gt; is selected from the main menu, and when
the game is closed.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;game_loop&lt;&#x2F;span&gt;&lt;span&gt;() -&amp;gt; impl EventRoutine&amp;lt;Return = (), Data = AppData, View = AppView, Event = CommonEvent&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;    make_either!(Ei &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; A &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;|&lt;&#x2F;span&gt;&lt;span&gt; B &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;|&lt;&#x2F;span&gt;&lt;span&gt; C &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;|&lt;&#x2F;span&gt;&lt;span&gt; D &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;|&lt;&#x2F;span&gt;&lt;span&gt; E);
&lt;&#x2F;span&gt;&lt;span&gt;    Loop::new(|| {
&lt;&#x2F;span&gt;&lt;span&gt;        GameEventRoutine.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;and_then&lt;&#x2F;span&gt;&lt;span&gt;(|game_return| &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;match&lt;&#x2F;span&gt;&lt;span&gt; game_return {
&lt;&#x2F;span&gt;&lt;span&gt;            GameReturn::Menu &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span&gt;Ei::A(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;main_menu&lt;&#x2F;span&gt;&lt;span&gt;().&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;and_then&lt;&#x2F;span&gt;&lt;span&gt;(|choice| {
&lt;&#x2F;span&gt;&lt;span&gt;                make_either!(Ei &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; A &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;|&lt;&#x2F;span&gt;&lt;span&gt; B &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;|&lt;&#x2F;span&gt;&lt;span&gt; C);
&lt;&#x2F;span&gt;&lt;span&gt;                &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;match&lt;&#x2F;span&gt;&lt;span&gt; choice {
&lt;&#x2F;span&gt;&lt;span&gt;                    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;                    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Ok&lt;&#x2F;span&gt;&lt;span&gt;(MainMenuEntry::SaveAndQuit) &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;                        Ei::C(SideEffect::new_with_view(|data: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut&lt;&#x2F;span&gt;&lt;span&gt; AppData, _: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;_| {
&lt;&#x2F;span&gt;&lt;span&gt;                            data.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;save_game&lt;&#x2F;span&gt;&lt;span&gt;();
&lt;&#x2F;span&gt;&lt;span&gt;                            &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Some&lt;&#x2F;span&gt;&lt;span&gt;(())
&lt;&#x2F;span&gt;&lt;span&gt;                        }))
&lt;&#x2F;span&gt;&lt;span&gt;                    }
&lt;&#x2F;span&gt;&lt;span&gt;                    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;                }
&lt;&#x2F;span&gt;&lt;span&gt;            })),
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;        })
&lt;&#x2F;span&gt;&lt;span&gt;    })
&lt;&#x2F;span&gt;&lt;span&gt;    .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;return_on_exit&lt;&#x2F;span&gt;&lt;span&gt;(|data| data.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;save_game&lt;&#x2F;span&gt;&lt;span&gt;())
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Reference implementation branch: &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;gridbugs&#x2F;chargrid-roguelike-tutorial-2020&#x2F;tree&#x2F;part-10.2&quot;&gt;part-10.2&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;h2 id=&quot;loading&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#loading&quot; aria-label=&quot;Anchor link for: loading&quot;&gt;Loading&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;Define a method in &lt;code&gt;AppData&lt;&#x2F;code&gt; which attempts to deserialize a game state from a file.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; app.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;impl &lt;&#x2F;span&gt;&lt;span&gt;AppData {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;load_game&lt;&#x2F;span&gt;&lt;span&gt;() -&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Option&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;GameState&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; file_storage &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= match &lt;&#x2F;span&gt;&lt;span&gt;FileStorage::next_to_exe(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;SAVE_DIR&lt;&#x2F;span&gt;&lt;span&gt;, IfDirectoryMissing::Create) {
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Ok&lt;&#x2F;span&gt;&lt;span&gt;(file_storage) &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt; file_storage,
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Err&lt;&#x2F;span&gt;&lt;span&gt;(error) &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;                eprintln!(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;Failed to load game: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;{:?}&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;, error);
&lt;&#x2F;span&gt;&lt;span&gt;                &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;return &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;None&lt;&#x2F;span&gt;&lt;span&gt;;
&lt;&#x2F;span&gt;&lt;span&gt;            }
&lt;&#x2F;span&gt;&lt;span&gt;        };
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;if !&lt;&#x2F;span&gt;&lt;span&gt;file_storage.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;exists&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;SAVE_FILE&lt;&#x2F;span&gt;&lt;span&gt;) {
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;return &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;None&lt;&#x2F;span&gt;&lt;span&gt;;
&lt;&#x2F;span&gt;&lt;span&gt;        }
&lt;&#x2F;span&gt;&lt;span&gt;        println!(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;Loading from &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;{:?}&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;, file_storage.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;full_path&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;SAVE_FILE&lt;&#x2F;span&gt;&lt;span&gt;));
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;match&lt;&#x2F;span&gt;&lt;span&gt; file_storage.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;load&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;SAVE_FILE&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;SAVE_FORMAT&lt;&#x2F;span&gt;&lt;span&gt;) {
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Ok&lt;&#x2F;span&gt;&lt;span&gt;(game_state) &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Some&lt;&#x2F;span&gt;&lt;span&gt;(game_state),
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Err&lt;&#x2F;span&gt;&lt;span&gt;(error) &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;                eprintln!(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;Failed to load game: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;{:?}&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;, error);
&lt;&#x2F;span&gt;&lt;span&gt;                &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;None
&lt;&#x2F;span&gt;&lt;span&gt;            }
&lt;&#x2F;span&gt;&lt;span&gt;        }
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;If it fails to deserialize the state, it prints a warning and continues.
This will likely happen from time to time, since the (de)serialization logic is derived from
the structure of the types used in the game. Whenever we change the definition of a type,
the game is no longer able to understand the serialized representation of the old versions
of these types.&lt;&#x2F;p&gt;
&lt;p&gt;Call &lt;code&gt;load_game&lt;&#x2F;code&gt; when creating a new &lt;code&gt;AppData&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;impl &lt;&#x2F;span&gt;&lt;span&gt;AppData {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;new&lt;&#x2F;span&gt;&lt;span&gt;(screen_size: Size, rng_seed: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;u64&lt;&#x2F;span&gt;&lt;span&gt;, visibility_algorithm: VisibilityAlgorithm) -&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;Self &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; game_state &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= Self&lt;&#x2F;span&gt;&lt;span&gt;::load_game()
&lt;&#x2F;span&gt;&lt;span&gt;            .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;unwrap_or_else&lt;&#x2F;span&gt;&lt;span&gt;(|| GameState::new(game_area_size, rng_seed, visibility_algorithm));
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Reference implementation branch: &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;gridbugs&#x2F;chargrid-roguelike-tutorial-2020&#x2F;tree&#x2F;part-10.3&quot;&gt;part-10.3&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;a href=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;roguelike-tutorial-2020-part-11&#x2F;&quot;&gt;Click here for the next part!&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Distro Hopping</title>
          <pubDate>Mon, 03 Aug 2020 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/daily/distro-hopping/</link>
          <guid>https://www.gridbugs.org/daily/distro-hopping/</guid>
          <description xml:base="https://www.gridbugs.org/daily/distro-hopping/">&lt;h2 id=&quot;first-post&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#first-post&quot; aria-label=&quot;Anchor link for: first-post&quot;&gt;First Post!&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;This is the beginning of an experiment where I write a short post every day.
The goal is to build up a log of what was at my mind at various points in time
so that I may spot trends or re-visit states of mind from the past. The average
post will likely be shorter and less specific than this one, but if I do it right
there will be one per day.&lt;&#x2F;p&gt;
&lt;p&gt;I’ve configured this site so that daily posts will appear at &lt;a href=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;daily&#x2F;&quot;&gt;&#x2F;daily&lt;&#x2F;a&gt;.
They may optionally contain a title (as this one does).&lt;&#x2F;p&gt;
&lt;h2 id=&quot;distro-hopping&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#distro-hopping&quot; aria-label=&quot;Anchor link for: distro-hopping&quot;&gt;Distro Hopping&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;I’ve been spending a long time over the past few weeks experimenting with FreeBSD and OpenBSD,
and even went so far as to install FreeBSD on my laptop (replacing archlinux) to see if
it is viable as a daily driver (so far so good!). Every now and again I flirt with the idea
of moving away from archlinux to something &lt;em&gt;even more&lt;&#x2F;em&gt; minimal. Setting up FreeBSD was mostly
painless once I worked out how to get the trackpad on my Lenovo T470 to work (which was &lt;em&gt;harrowing&lt;&#x2F;em&gt;
(if you’re having trouble with this too, read &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;gridbugs&#x2F;dotfiles&#x2F;blob&#x2F;master&#x2F;README.FreeBSD.md&quot;&gt;this&lt;&#x2F;a&gt;)).&lt;&#x2F;p&gt;
&lt;p&gt;There’s nothing about arch
(or linux in general for that matter) that I find particularly problematic. Hating on systemd
has become something of a meme at this point, but I haven’t done enough research to have strong
feelings one way or another.&lt;&#x2F;p&gt;
&lt;p&gt;A phrase I’ve found myself repeating recently is “archlinux was a good OS for my twenties”.
I’m 28, and I’ve been using arch for at least 6 years as the main operating system on all my
computers. Being on the bleeding edge hasn’t been as chaotic as one might think. Notable
exceptions include gnome2 being upgraded to gnome3 (a completely different
desktop environment) with no warning, and pixel font support being pulled from almost all applications
(it was really just pulled from harfbuzz), which was a blessing in disguise as it led me to discover
&lt;a href=&quot;http:&#x2F;&#x2F;st.suckless.org&#x2F;&quot;&gt;st&lt;&#x2F;a&gt; which I’ve been happily using ever since.
It was fun and exciting and it taught me a lot. The &lt;a href=&quot;https:&#x2F;&#x2F;wiki.archlinux.org&#x2F;&quot;&gt;wiki&lt;&#x2F;a&gt; is by far the
most reliable and comprehensive source of unix knowledge I’ve found, and I’m sure I’ll continue to rely
on it regardless of which OS I run in my thirties.&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Part 9 - Ranged Scrolls and Targeting</title>
          <pubDate>Wed, 29 Jul 2020 19:00:00 +1000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/roguelike-tutorial-2020-part-9/</link>
          <guid>https://www.gridbugs.org/roguelike-tutorial-2020-part-9/</guid>
          <description xml:base="https://www.gridbugs.org/roguelike-tutorial-2020-part-9/">&lt;p&gt;In this part we’ll introduce ranged scrolls and targeting.&lt;&#x2F;p&gt;
&lt;p&gt;By the end of this part it will be possible to launch fireballs and confusion spells.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;roguelike-tutorial-2020-part-9&#x2F;launch.png&quot; alt=&quot;launch.png&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;This part is loosely based on &lt;a href=&quot;http:&#x2F;&#x2F;rogueliketutorials.com&#x2F;tutorials&#x2F;tcod&#x2F;part-9&#x2F;&quot;&gt;this part&lt;&#x2F;a&gt; of the
python tcod tutorial.&lt;&#x2F;p&gt;
&lt;p&gt;Reference implementation branch for starting point: &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;gridbugs&#x2F;chargrid-roguelike-tutorial-2020&#x2F;tree&#x2F;part-8-end&quot;&gt;part-8-end&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;p&gt;In this post:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;roguelike-tutorial-2020-part-9&#x2F;#examine-command&quot;&gt;Examine Command&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;roguelike-tutorial-2020-part-9&#x2F;#fireball-scroll&quot;&gt;Fireball Scroll&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;roguelike-tutorial-2020-part-9&#x2F;#launching-fireballs&quot;&gt;Launching Fireballs&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;roguelike-tutorial-2020-part-9&#x2F;#confusion-scroll&quot;&gt;Confusion Scroll&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;examine-command&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#examine-command&quot; aria-label=&quot;Anchor link for: examine-command&quot;&gt;Examine Command&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;As a first step towards ranged abilities, add an examine command that lets th player
use the arrow keys and mouse to move a cursor over the game area.
We’ll add a section to the UI for showing the name of the character or item at the
current cursor position.&lt;&#x2F;p&gt;
&lt;p&gt;We’ll also allow the player to use the mouse to examine a cell during normal gameplay.&lt;&#x2F;p&gt;
&lt;p&gt;Add a type enumerating all the different results of examining a cell.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; game.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;#[derive(Clone, Copy, Debug)]
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;pub enum &lt;&#x2F;span&gt;&lt;span&gt;ExamineCell {
&lt;&#x2F;span&gt;&lt;span&gt;    Npc(NpcType),
&lt;&#x2F;span&gt;&lt;span&gt;    NpcCorpse(NpcType),
&lt;&#x2F;span&gt;&lt;span&gt;    Item(ItemType),
&lt;&#x2F;span&gt;&lt;span&gt;    Player,
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Add a method to &lt;code&gt;World&lt;&#x2F;code&gt; for examining a cell.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; world.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;use crate&lt;&#x2F;span&gt;&lt;span&gt;::game::{ExamineCell, LogMessage};
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;impl &lt;&#x2F;span&gt;&lt;span&gt;World {
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;pub fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;examine_cell&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;self, coord: Coord) -&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Option&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;ExamineCell&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; layers &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span&gt;self.spatial_table.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;layers_at&lt;&#x2F;span&gt;&lt;span&gt;(coord)&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;?&lt;&#x2F;span&gt;&lt;span&gt;;
&lt;&#x2F;span&gt;&lt;span&gt;        layers
&lt;&#x2F;span&gt;&lt;span&gt;            .character
&lt;&#x2F;span&gt;&lt;span&gt;            .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;or_else&lt;&#x2F;span&gt;&lt;span&gt;(|| layers.object)
&lt;&#x2F;span&gt;&lt;span&gt;            .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;and_then&lt;&#x2F;span&gt;&lt;span&gt;(|entity| {
&lt;&#x2F;span&gt;&lt;span&gt;                self.components
&lt;&#x2F;span&gt;&lt;span&gt;                    .tile
&lt;&#x2F;span&gt;&lt;span&gt;                    .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;get&lt;&#x2F;span&gt;&lt;span&gt;(entity)
&lt;&#x2F;span&gt;&lt;span&gt;                    .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;and_then&lt;&#x2F;span&gt;&lt;span&gt;(|&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;tile| &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;match&lt;&#x2F;span&gt;&lt;span&gt; tile {
&lt;&#x2F;span&gt;&lt;span&gt;                        Tile::Npc(npc_type) &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Some&lt;&#x2F;span&gt;&lt;span&gt;(ExamineCell::Npc(npc_type)),
&lt;&#x2F;span&gt;&lt;span&gt;                        Tile::NpcCorpse(npc_type) &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Some&lt;&#x2F;span&gt;&lt;span&gt;(ExamineCell::NpcCorpse(npc_type)),
&lt;&#x2F;span&gt;&lt;span&gt;                        Tile::Item(item_type) &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Some&lt;&#x2F;span&gt;&lt;span&gt;(ExamineCell::Item(item_type)),
&lt;&#x2F;span&gt;&lt;span&gt;                        Tile::Player &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Some&lt;&#x2F;span&gt;&lt;span&gt;(ExamineCell::Player),
&lt;&#x2F;span&gt;&lt;span&gt;                        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;_ =&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;None&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;                    })
&lt;&#x2F;span&gt;&lt;span&gt;            })
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Add a method to &lt;code&gt;GameState&lt;&#x2F;code&gt; for examining a cell at a coordinate &lt;strong&gt;if it is currently visible to the player&lt;&#x2F;strong&gt;.
Also add a method returning the player’s current coordinate which will come in handy soon.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; game.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;impl &lt;&#x2F;span&gt;&lt;span&gt;GameState {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;pub fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;player_coord&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;self) -&amp;gt; Coord {
&lt;&#x2F;span&gt;&lt;span&gt;        self.world
&lt;&#x2F;span&gt;&lt;span&gt;            .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;entity_coord&lt;&#x2F;span&gt;&lt;span&gt;(self.player_entity)
&lt;&#x2F;span&gt;&lt;span&gt;            .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;expect&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;player has no coord&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;pub fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;examine_cell&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;self, coord: Coord) -&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Option&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;ExamineCell&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;match &lt;&#x2F;span&gt;&lt;span&gt;self.visibility_grid.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;cell_visibility&lt;&#x2F;span&gt;&lt;span&gt;(coord) {
&lt;&#x2F;span&gt;&lt;span&gt;            CellVisibility::Currently &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span&gt;self.world.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;examine_cell&lt;&#x2F;span&gt;&lt;span&gt;(coord),
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;_ =&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;None&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;        }
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Update the UI to have it render the currently-examined cell (if any).
Also, when the cursor is controlled by the arrow keys, we’ll display a string
to indicate what the cursor is for. Currently it will just be for examining cells,
but later it will be for aiming spells as well.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; ui.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;use crate&lt;&#x2F;span&gt;&lt;span&gt;::game::{ExamineCell, LogMessage};
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;use &lt;&#x2F;span&gt;&lt;span&gt;chargrid::{
&lt;&#x2F;span&gt;&lt;span&gt;    decorator::{AlignView, Alignment, AlignmentX, AlignmentY, BoundView},
&lt;&#x2F;span&gt;&lt;span&gt;    text::{wrap, RichTextPartOwned, RichTextViewSingleLine, StringView, StringViewSingleLine},
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;};
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;examine_cell_str&lt;&#x2F;span&gt;&lt;span&gt;(examine_cell: ExamineCell) -&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&amp;#39;static str &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;match&lt;&#x2F;span&gt;&lt;span&gt; examine_cell {
&lt;&#x2F;span&gt;&lt;span&gt;        ExamineCell::Npc(npc_type) &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;| &lt;&#x2F;span&gt;&lt;span&gt;ExamineCell::NpcCorpse(npc_type) &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt; npc_type.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;name&lt;&#x2F;span&gt;&lt;span&gt;(),
&lt;&#x2F;span&gt;&lt;span&gt;        ExamineCell::Item(item_type) &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt; item_type.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;name&lt;&#x2F;span&gt;&lt;span&gt;(),
&lt;&#x2F;span&gt;&lt;span&gt;        ExamineCell::Player &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;yourself&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;pub struct &lt;&#x2F;span&gt;&lt;span&gt;UiData&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;#39;a&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;    ...
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;pub &lt;&#x2F;span&gt;&lt;span&gt;name: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Option&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&amp;#39;static str&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;,
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;pub &lt;&#x2F;span&gt;&lt;span&gt;examine_cell: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Option&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;ExamineCell&amp;gt;,
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;impl&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;#39;a&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt; View&amp;lt;UiData&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;#39;a&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;for &lt;&#x2F;span&gt;&lt;span&gt;UiView {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;view&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;F: Frame, C: ColModify&amp;gt;(
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut &lt;&#x2F;span&gt;&lt;span&gt;self,
&lt;&#x2F;span&gt;&lt;span&gt;        data: UiData,
&lt;&#x2F;span&gt;&lt;span&gt;        context: ViewContext&amp;lt;C&amp;gt;,
&lt;&#x2F;span&gt;&lt;span&gt;        frame: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut&lt;&#x2F;span&gt;&lt;span&gt; F,
&lt;&#x2F;span&gt;&lt;span&gt;    ) {
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;if let &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Some&lt;&#x2F;span&gt;&lt;span&gt;(name) &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; data.name {
&lt;&#x2F;span&gt;&lt;span&gt;            BoundView {
&lt;&#x2F;span&gt;&lt;span&gt;                size: Size::new(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;HEALTH_WIDTH&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;),
&lt;&#x2F;span&gt;&lt;span&gt;                view: AlignView {
&lt;&#x2F;span&gt;&lt;span&gt;                    alignment: Alignment::centre(),
&lt;&#x2F;span&gt;&lt;span&gt;                    view: StringViewSingleLine::new(
&lt;&#x2F;span&gt;&lt;span&gt;                        Style::new().&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;with_foreground&lt;&#x2F;span&gt;&lt;span&gt;(Rgb24::new_grey(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;255&lt;&#x2F;span&gt;&lt;span&gt;)),
&lt;&#x2F;span&gt;&lt;span&gt;                    ),
&lt;&#x2F;span&gt;&lt;span&gt;                },
&lt;&#x2F;span&gt;&lt;span&gt;            }
&lt;&#x2F;span&gt;&lt;span&gt;            .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;view&lt;&#x2F;span&gt;&lt;span&gt;(name, context.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;add_offset&lt;&#x2F;span&gt;&lt;span&gt;(Coord::new(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;)), frame);
&lt;&#x2F;span&gt;&lt;span&gt;        }
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;if let &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Some&lt;&#x2F;span&gt;&lt;span&gt;(examine_cell) &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; data.examine_cell {
&lt;&#x2F;span&gt;&lt;span&gt;            BoundView {
&lt;&#x2F;span&gt;&lt;span&gt;                size: Size::new(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;HEALTH_WIDTH&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;2&lt;&#x2F;span&gt;&lt;span&gt;),
&lt;&#x2F;span&gt;&lt;span&gt;                view: AlignView {
&lt;&#x2F;span&gt;&lt;span&gt;                    alignment: Alignment {
&lt;&#x2F;span&gt;&lt;span&gt;                        x: AlignmentX::Centre,
&lt;&#x2F;span&gt;&lt;span&gt;                        y: AlignmentY::Bottom,
&lt;&#x2F;span&gt;&lt;span&gt;                    },
&lt;&#x2F;span&gt;&lt;span&gt;                    view: StringView::new(
&lt;&#x2F;span&gt;&lt;span&gt;                        Style::new().&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;with_foreground&lt;&#x2F;span&gt;&lt;span&gt;(Rgb24::new_grey(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;187&lt;&#x2F;span&gt;&lt;span&gt;)),
&lt;&#x2F;span&gt;&lt;span&gt;                        wrap::Word::new(),
&lt;&#x2F;span&gt;&lt;span&gt;                    ),
&lt;&#x2F;span&gt;&lt;span&gt;                },
&lt;&#x2F;span&gt;&lt;span&gt;            }
&lt;&#x2F;span&gt;&lt;span&gt;            .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;view&lt;&#x2F;span&gt;&lt;span&gt;(
&lt;&#x2F;span&gt;&lt;span&gt;                &lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;examine_cell_str&lt;&#x2F;span&gt;&lt;span&gt;(examine_cell),
&lt;&#x2F;span&gt;&lt;span&gt;                context.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;add_offset&lt;&#x2F;span&gt;&lt;span&gt;(Coord::new(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;2&lt;&#x2F;span&gt;&lt;span&gt;)),
&lt;&#x2F;span&gt;&lt;span&gt;                frame,
&lt;&#x2F;span&gt;&lt;span&gt;            );
&lt;&#x2F;span&gt;&lt;span&gt;        }
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Add a field to &lt;code&gt;AppState&lt;&#x2F;code&gt; containing the current cursor position if any.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; app.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;struct &lt;&#x2F;span&gt;&lt;span&gt;AppData {
&lt;&#x2F;span&gt;&lt;span&gt;    ...
&lt;&#x2F;span&gt;&lt;span&gt;    cursor: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Option&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;Coord&amp;gt;,
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;impl &lt;&#x2F;span&gt;&lt;span&gt;AppData {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;new&lt;&#x2F;span&gt;&lt;span&gt;(screen_size: Size, rng_seed: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;u64&lt;&#x2F;span&gt;&lt;span&gt;, visibility_algorithm: VisibilityAlgorithm) -&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;Self &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;Self &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;            cursor: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;None&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;        }
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Update &lt;code&gt;AppView::render_ui&lt;&#x2F;code&gt; to take the name of the current cursor mode, and have it render the cursor
and pass the result of examining the cell under the cursor to the UI renderer.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; app.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;use &lt;&#x2F;span&gt;&lt;span&gt;chargrid::{
&lt;&#x2F;span&gt;&lt;span&gt;    render::{blend_mode, ColModify, ColModifyMap, Frame, Style, View, ViewCell, ViewContext},
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;impl &lt;&#x2F;span&gt;&lt;span&gt;AppView {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;render_ui&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;F: Frame, C: ColModify&amp;gt;(
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut &lt;&#x2F;span&gt;&lt;span&gt;self,
&lt;&#x2F;span&gt;&lt;span&gt;        name: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Option&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&amp;#39;static str&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;,
&lt;&#x2F;span&gt;&lt;span&gt;        data: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;AppData,
&lt;&#x2F;span&gt;&lt;span&gt;        context: ViewContext&amp;lt;C&amp;gt;,
&lt;&#x2F;span&gt;&lt;span&gt;        frame: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut&lt;&#x2F;span&gt;&lt;span&gt; F,
&lt;&#x2F;span&gt;&lt;span&gt;    ) {
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; examine_cell &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= if let &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Some&lt;&#x2F;span&gt;&lt;span&gt;(cursor) &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; data.cursor {
&lt;&#x2F;span&gt;&lt;span&gt;            frame.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;blend_cell_background_relative&lt;&#x2F;span&gt;&lt;span&gt;(
&lt;&#x2F;span&gt;&lt;span&gt;                cursor,
&lt;&#x2F;span&gt;&lt;span&gt;                &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;                Rgb24::new_grey(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;255&lt;&#x2F;span&gt;&lt;span&gt;),
&lt;&#x2F;span&gt;&lt;span&gt;                &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;127&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;                blend_mode::LinearInterpolate,
&lt;&#x2F;span&gt;&lt;span&gt;                context,
&lt;&#x2F;span&gt;&lt;span&gt;            );
&lt;&#x2F;span&gt;&lt;span&gt;            data.game_state.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;examine_cell&lt;&#x2F;span&gt;&lt;span&gt;(cursor)
&lt;&#x2F;span&gt;&lt;span&gt;        } &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;else &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;None
&lt;&#x2F;span&gt;&lt;span&gt;        };
&lt;&#x2F;span&gt;&lt;span&gt;        self.ui_view.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;view&lt;&#x2F;span&gt;&lt;span&gt;(
&lt;&#x2F;span&gt;&lt;span&gt;            UiData {
&lt;&#x2F;span&gt;&lt;span&gt;                player_hit_points,
&lt;&#x2F;span&gt;&lt;span&gt;                messages,
&lt;&#x2F;span&gt;&lt;span&gt;                name,
&lt;&#x2F;span&gt;&lt;span&gt;                examine_cell,
&lt;&#x2F;span&gt;&lt;span&gt;            },
&lt;&#x2F;span&gt;&lt;span&gt;            context.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;add_offset&lt;&#x2F;span&gt;&lt;span&gt;(Coord::new(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt;, self.ui_y_offset)),
&lt;&#x2F;span&gt;&lt;span&gt;            frame,
&lt;&#x2F;span&gt;&lt;span&gt;        );
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Now go update all the places where &lt;code&gt;AppView::render_ui&lt;&#x2F;code&gt; gets called and pass &lt;code&gt;None&lt;&#x2F;code&gt; as its &lt;code&gt;name&lt;&#x2F;code&gt; argument.&lt;&#x2F;p&gt;
&lt;p&gt;Update &lt;code&gt;AppData::handle_input&lt;&#x2F;code&gt; so that moving the mouse during normal gameplay sets the cursor position,
and pressing a key clears the cursor. This will let the player use the mouse to examine cells, even when
not in “examine” mode.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; app.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;use &lt;&#x2F;span&gt;&lt;span&gt;chargrid::{
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;    input::{keys, Input, KeyboardInput, MouseButton, MouseInput},
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;impl &lt;&#x2F;span&gt;&lt;span&gt;AppData {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;handle_input&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut &lt;&#x2F;span&gt;&lt;span&gt;self, input: Input) -&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Option&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;GameReturn&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;match&lt;&#x2F;span&gt;&lt;span&gt; input {
&lt;&#x2F;span&gt;&lt;span&gt;            Input::Keyboard(key) &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;                &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;match&lt;&#x2F;span&gt;&lt;span&gt; key {
&lt;&#x2F;span&gt;&lt;span&gt;                    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;                }
&lt;&#x2F;span&gt;&lt;span&gt;                self.cursor &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;None&lt;&#x2F;span&gt;&lt;span&gt;;
&lt;&#x2F;span&gt;&lt;span&gt;            }
&lt;&#x2F;span&gt;&lt;span&gt;            Input::Mouse(mouse_input) &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; match&lt;&#x2F;span&gt;&lt;span&gt; mouse_input {
&lt;&#x2F;span&gt;&lt;span&gt;                MouseInput::MouseMove { coord, &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;.. &lt;&#x2F;span&gt;&lt;span&gt;} &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span&gt;self.cursor &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Some&lt;&#x2F;span&gt;&lt;span&gt;(coord),
&lt;&#x2F;span&gt;&lt;span&gt;                &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;_ =&amp;gt; &lt;&#x2F;span&gt;&lt;span&gt;(),
&lt;&#x2F;span&gt;&lt;span&gt;            },
&lt;&#x2F;span&gt;&lt;span&gt;        }
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Add &lt;code&gt;TargetEventRoutine&lt;&#x2F;code&gt; - an &lt;code&gt;EventRoutine&lt;&#x2F;code&gt; in which the cursor can be controlled using the arrow keys as well
as the mouse. It has a string field which is the name of the target mode. This is the string that we’ll show in
the bottom-left corner.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; app.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;struct &lt;&#x2F;span&gt;&lt;span&gt;TargetEventRoutine {
&lt;&#x2F;span&gt;&lt;span&gt;    name: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&amp;#39;static str&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;impl &lt;&#x2F;span&gt;&lt;span&gt;EventRoutine &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;for &lt;&#x2F;span&gt;&lt;span&gt;TargetEventRoutine {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;type &lt;&#x2F;span&gt;&lt;span&gt;Return &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Option&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;Coord&amp;gt;;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;type &lt;&#x2F;span&gt;&lt;span&gt;Data &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; AppData;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;type &lt;&#x2F;span&gt;&lt;span&gt;View &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; AppView;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;type &lt;&#x2F;span&gt;&lt;span&gt;Event &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; CommonEvent;
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;handle&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;EP&amp;gt;(
&lt;&#x2F;span&gt;&lt;span&gt;        self,
&lt;&#x2F;span&gt;&lt;span&gt;        data: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut Self::&lt;&#x2F;span&gt;&lt;span&gt;Data,
&lt;&#x2F;span&gt;&lt;span&gt;        _view: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;Self::&lt;&#x2F;span&gt;&lt;span&gt;View,
&lt;&#x2F;span&gt;&lt;span&gt;        event_or_peek: EP,
&lt;&#x2F;span&gt;&lt;span&gt;    ) -&amp;gt; Handled&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;Self::&lt;&#x2F;span&gt;&lt;span&gt;Return, &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;Self&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;where
&lt;&#x2F;span&gt;&lt;span&gt;        EP: EventOrPeek&amp;lt;Event = &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;Self::&lt;&#x2F;span&gt;&lt;span&gt;Event&amp;gt;,
&lt;&#x2F;span&gt;&lt;span&gt;    {
&lt;&#x2F;span&gt;&lt;span&gt;        event_routine::event_or_peek_with_handled(event_or_peek, self, |s, event| {
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;match&lt;&#x2F;span&gt;&lt;span&gt; event {
&lt;&#x2F;span&gt;&lt;span&gt;                CommonEvent::Input(input) &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; match&lt;&#x2F;span&gt;&lt;span&gt; input {
&lt;&#x2F;span&gt;&lt;span&gt;                    Input::Keyboard(key) &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;                        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; delta &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= match&lt;&#x2F;span&gt;&lt;span&gt; key {
&lt;&#x2F;span&gt;&lt;span&gt;                            KeyboardInput::Left &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span&gt;Coord::new(&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;-&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt;),
&lt;&#x2F;span&gt;&lt;span&gt;                            KeyboardInput::Right &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span&gt;Coord::new(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt;),
&lt;&#x2F;span&gt;&lt;span&gt;                            KeyboardInput::Up &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span&gt;Coord::new(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;-&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;),
&lt;&#x2F;span&gt;&lt;span&gt;                            KeyboardInput::Down &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span&gt;Coord::new(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;),
&lt;&#x2F;span&gt;&lt;span&gt;                            keys::&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;RETURN &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;                                &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; cursor &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; data.cursor;
&lt;&#x2F;span&gt;&lt;span&gt;                                data.cursor &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;None&lt;&#x2F;span&gt;&lt;span&gt;;
&lt;&#x2F;span&gt;&lt;span&gt;                                &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;return &lt;&#x2F;span&gt;&lt;span&gt;Handled::Return(cursor);
&lt;&#x2F;span&gt;&lt;span&gt;                            }
&lt;&#x2F;span&gt;&lt;span&gt;                            keys::&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;ESCAPE &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;                                data.cursor &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;None&lt;&#x2F;span&gt;&lt;span&gt;;
&lt;&#x2F;span&gt;&lt;span&gt;                                &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;return &lt;&#x2F;span&gt;&lt;span&gt;Handled::Return(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;None&lt;&#x2F;span&gt;&lt;span&gt;);
&lt;&#x2F;span&gt;&lt;span&gt;                            }
&lt;&#x2F;span&gt;&lt;span&gt;                            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;_ =&amp;gt; &lt;&#x2F;span&gt;&lt;span&gt;Coord::new(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt;),
&lt;&#x2F;span&gt;&lt;span&gt;                        };
&lt;&#x2F;span&gt;&lt;span&gt;                        data.cursor &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Some&lt;&#x2F;span&gt;&lt;span&gt;(
&lt;&#x2F;span&gt;&lt;span&gt;                            data.cursor
&lt;&#x2F;span&gt;&lt;span&gt;                                .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;unwrap_or_else&lt;&#x2F;span&gt;&lt;span&gt;(|| data.game_state.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;player_coord&lt;&#x2F;span&gt;&lt;span&gt;())
&lt;&#x2F;span&gt;&lt;span&gt;                                &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;+&lt;&#x2F;span&gt;&lt;span&gt; delta,
&lt;&#x2F;span&gt;&lt;span&gt;                        );
&lt;&#x2F;span&gt;&lt;span&gt;                    }
&lt;&#x2F;span&gt;&lt;span&gt;                    Input::Mouse(mouse_input) &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; match&lt;&#x2F;span&gt;&lt;span&gt; mouse_input {
&lt;&#x2F;span&gt;&lt;span&gt;                        MouseInput::MouseMove { coord, &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;.. &lt;&#x2F;span&gt;&lt;span&gt;} &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt; data.cursor &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Some&lt;&#x2F;span&gt;&lt;span&gt;(coord),
&lt;&#x2F;span&gt;&lt;span&gt;                        MouseInput::MousePress {
&lt;&#x2F;span&gt;&lt;span&gt;                            button: MouseButton::Left,
&lt;&#x2F;span&gt;&lt;span&gt;                            coord,
&lt;&#x2F;span&gt;&lt;span&gt;                        } &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;                            data.cursor &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;None&lt;&#x2F;span&gt;&lt;span&gt;;
&lt;&#x2F;span&gt;&lt;span&gt;                            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;return &lt;&#x2F;span&gt;&lt;span&gt;Handled::Return(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Some&lt;&#x2F;span&gt;&lt;span&gt;(coord));
&lt;&#x2F;span&gt;&lt;span&gt;                        }
&lt;&#x2F;span&gt;&lt;span&gt;                        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;_ =&amp;gt; &lt;&#x2F;span&gt;&lt;span&gt;(),
&lt;&#x2F;span&gt;&lt;span&gt;                    },
&lt;&#x2F;span&gt;&lt;span&gt;                },
&lt;&#x2F;span&gt;&lt;span&gt;                CommonEvent::Frame(_period) &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span&gt;(),
&lt;&#x2F;span&gt;&lt;span&gt;            };
&lt;&#x2F;span&gt;&lt;span&gt;            Handled::Continue(s)
&lt;&#x2F;span&gt;&lt;span&gt;        })
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;view&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;F, C&amp;gt;(
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;self,
&lt;&#x2F;span&gt;&lt;span&gt;        data: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;Self::&lt;&#x2F;span&gt;&lt;span&gt;Data,
&lt;&#x2F;span&gt;&lt;span&gt;        view: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut Self::&lt;&#x2F;span&gt;&lt;span&gt;View,
&lt;&#x2F;span&gt;&lt;span&gt;        context: ViewContext&amp;lt;C&amp;gt;,
&lt;&#x2F;span&gt;&lt;span&gt;        frame: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut&lt;&#x2F;span&gt;&lt;span&gt; F,
&lt;&#x2F;span&gt;&lt;span&gt;    ) &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;where
&lt;&#x2F;span&gt;&lt;span&gt;        F: Frame,
&lt;&#x2F;span&gt;&lt;span&gt;        C: ColModify,
&lt;&#x2F;span&gt;&lt;span&gt;    {
&lt;&#x2F;span&gt;&lt;span&gt;        view.game_view.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;view&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;data.game_state, context, frame);
&lt;&#x2F;span&gt;&lt;span&gt;        view.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;render_ui&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Some&lt;&#x2F;span&gt;&lt;span&gt;(self.name), &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;data, context, frame);
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Now update &lt;code&gt;AppData::handle_input&lt;&#x2F;code&gt; again so that when the ‘x’ key is pressed, we run the &lt;code&gt;TargetEventRoutine&lt;&#x2F;code&gt;
so the player can examine cells moving the cursor with the arrow keys.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; app.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;enum &lt;&#x2F;span&gt;&lt;span&gt;GameReturn {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;    Examine,
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;impl &lt;&#x2F;span&gt;&lt;span&gt;AppData {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;handle_input&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut &lt;&#x2F;span&gt;&lt;span&gt;self, input: Input) -&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Option&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;GameReturn&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;match&lt;&#x2F;span&gt;&lt;span&gt; input {
&lt;&#x2F;span&gt;&lt;span&gt;            Input::Keyboard(key) &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;                &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;match&lt;&#x2F;span&gt;&lt;span&gt; key {
&lt;&#x2F;span&gt;&lt;span&gt;                    KeyboardInput::Char(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;#39;x&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;) &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;                        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;if &lt;&#x2F;span&gt;&lt;span&gt;self.cursor.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;is_none&lt;&#x2F;span&gt;&lt;span&gt;() {
&lt;&#x2F;span&gt;&lt;span&gt;                            self.cursor &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Some&lt;&#x2F;span&gt;&lt;span&gt;(self.game_state.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;player_coord&lt;&#x2F;span&gt;&lt;span&gt;());
&lt;&#x2F;span&gt;&lt;span&gt;                        }
&lt;&#x2F;span&gt;&lt;span&gt;                        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;return &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Some&lt;&#x2F;span&gt;&lt;span&gt;(GameReturn::Examine);
&lt;&#x2F;span&gt;&lt;span&gt;                    }
&lt;&#x2F;span&gt;&lt;span&gt;                    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;                }
&lt;&#x2F;span&gt;&lt;span&gt;                &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;            }
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;        }
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;game_loop&lt;&#x2F;span&gt;&lt;span&gt;() -&amp;gt; impl EventRoutine&amp;lt;Return = (), Data = AppData, View = AppView, Event = CommonEvent&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;    make_either!(Ei &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; A &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;|&lt;&#x2F;span&gt;&lt;span&gt; B &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;|&lt;&#x2F;span&gt;&lt;span&gt; C &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;|&lt;&#x2F;span&gt;&lt;span&gt; D &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;|&lt;&#x2F;span&gt;&lt;span&gt; E);
&lt;&#x2F;span&gt;&lt;span&gt;    Loop::new(|| {
&lt;&#x2F;span&gt;&lt;span&gt;        GameEventRoutine.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;and_then&lt;&#x2F;span&gt;&lt;span&gt;(|game_return| &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;match&lt;&#x2F;span&gt;&lt;span&gt; game_return {
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;            GameReturn::Examine &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span&gt;Ei::E(TargetEventRoutine { name: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;EXAMINE&amp;quot; &lt;&#x2F;span&gt;&lt;span&gt;}.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;map&lt;&#x2F;span&gt;&lt;span&gt;(|_| &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;None&lt;&#x2F;span&gt;&lt;span&gt;)),
&lt;&#x2F;span&gt;&lt;span&gt;        })
&lt;&#x2F;span&gt;&lt;span&gt;    }).&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;return_on_exit&lt;&#x2F;span&gt;&lt;span&gt;(|_| ())
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;roguelike-tutorial-2020-part-9&#x2F;examine.png&quot; alt=&quot;examine.png&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Reference implementation branch: &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;gridbugs&#x2F;chargrid-roguelike-tutorial-2020&#x2F;tree&#x2F;part-9.0&quot;&gt;part-9.0&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;h2 id=&quot;fireball-scroll&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#fireball-scroll&quot; aria-label=&quot;Anchor link for: fireball-scroll&quot;&gt;Fireball Scroll&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;The first ranged item we’ll add will be fireball scrolls. Add &lt;code&gt;FireballScroll&lt;&#x2F;code&gt; as a new item type.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; world.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;pub enum &lt;&#x2F;span&gt;&lt;span&gt;ItemType {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;    FireballScroll,
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;impl &lt;&#x2F;span&gt;&lt;span&gt;ItemType {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;pub fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;name&lt;&#x2F;span&gt;&lt;span&gt;(self) -&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&amp;#39;static str &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;match &lt;&#x2F;span&gt;&lt;span&gt;self {
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;Self&lt;&#x2F;span&gt;&lt;span&gt;::FireballScroll &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;fireball scroll&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;        }
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;impl &lt;&#x2F;span&gt;&lt;span&gt;World {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;pub fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;maybe_use_item&lt;&#x2F;span&gt;&lt;span&gt;(
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut &lt;&#x2F;span&gt;&lt;span&gt;self,
&lt;&#x2F;span&gt;&lt;span&gt;        character: Entity,
&lt;&#x2F;span&gt;&lt;span&gt;        inventory_index: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;usize&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;        message_log: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Vec&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;LogMessage&amp;gt;,
&lt;&#x2F;span&gt;&lt;span&gt;    ) -&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Result&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;(), ()&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;match&lt;&#x2F;span&gt;&lt;span&gt; item_type {
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;            ItemType::FireballScroll &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;                println!(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;todo&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;);
&lt;&#x2F;span&gt;&lt;span&gt;            }
&lt;&#x2F;span&gt;&lt;span&gt;        }
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Ok&lt;&#x2F;span&gt;&lt;span&gt;(())
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; app.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;pub mod &lt;&#x2F;span&gt;&lt;span&gt;colours {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;pub const &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;FIREBALL_SCROLL&lt;&#x2F;span&gt;&lt;span&gt;: Rgb24 &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span&gt;Rgb24::new(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;255&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;127&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt;);
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;pub fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;item_colour&lt;&#x2F;span&gt;&lt;span&gt;(item_type: ItemType) -&amp;gt; Rgb24 {
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;match&lt;&#x2F;span&gt;&lt;span&gt; item_type {
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;            ItemType::FireballScroll &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;FIREBALL_SCROLL&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;        }
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;currently_visible_view_cell_of_tile&lt;&#x2F;span&gt;&lt;span&gt;(tile: Tile) -&amp;gt; ViewCell {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;match&lt;&#x2F;span&gt;&lt;span&gt; tile {
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;        Tile::Item(ItemType::FireballScroll) &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span&gt;ViewCell::new()
&lt;&#x2F;span&gt;&lt;span&gt;            .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;with_character&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;#39;♫&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;)
&lt;&#x2F;span&gt;&lt;span&gt;            .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;with_foreground&lt;&#x2F;span&gt;&lt;span&gt;(colours::&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;FIREBALL_SCROLL&lt;&#x2F;span&gt;&lt;span&gt;),
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Update the dungeon generator to place fireball scrolls.
Generalize the logic which places health potions to place all items.
For now gives fireball scrolls a 100% chance of spawning to make it easier to test.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; terrain.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;impl &lt;&#x2F;span&gt;&lt;span&gt;Room {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; Place `n` items at random positions within the room
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;place_items&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;R: Rng&amp;gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;self, n: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;usize&lt;&#x2F;span&gt;&lt;span&gt;, grid: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut &lt;&#x2F;span&gt;&lt;span&gt;Grid&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Option&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;TerrainTile&amp;gt;&amp;gt;, rng: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut&lt;&#x2F;span&gt;&lt;span&gt; R) {
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;for&lt;&#x2F;span&gt;&lt;span&gt; coord &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;in &lt;&#x2F;span&gt;&lt;span&gt;self
&lt;&#x2F;span&gt;&lt;span&gt;            .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;coords&lt;&#x2F;span&gt;&lt;span&gt;()
&lt;&#x2F;span&gt;&lt;span&gt;            .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;filter&lt;&#x2F;span&gt;&lt;span&gt;(|&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;coord| grid.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;get_checked&lt;&#x2F;span&gt;&lt;span&gt;(coord).&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;unwrap&lt;&#x2F;span&gt;&lt;span&gt;() &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;== &lt;&#x2F;span&gt;&lt;span&gt;TerrainTile::Floor)
&lt;&#x2F;span&gt;&lt;span&gt;            .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;choose_multiple&lt;&#x2F;span&gt;&lt;span&gt;(rng, n)
&lt;&#x2F;span&gt;&lt;span&gt;        {
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; item &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= match&lt;&#x2F;span&gt;&lt;span&gt; rng.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;gen_range&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;..&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;100&lt;&#x2F;span&gt;&lt;span&gt;) {
&lt;&#x2F;span&gt;&lt;span&gt;                &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;..=&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;100 &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span&gt;ItemType::FireballScroll,
&lt;&#x2F;span&gt;&lt;span&gt;                &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;_ =&amp;gt; &lt;&#x2F;span&gt;&lt;span&gt;ItemType::HealthPotion,
&lt;&#x2F;span&gt;&lt;span&gt;            };
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;*&lt;&#x2F;span&gt;&lt;span&gt;grid.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;get_checked_mut&lt;&#x2F;span&gt;&lt;span&gt;(coord) &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Some&lt;&#x2F;span&gt;&lt;span&gt;(TerrainTile::Item(item));
&lt;&#x2F;span&gt;&lt;span&gt;        }
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;pub fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;generate_dungeon&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;R: Rng&amp;gt;(size: Size, rng: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut&lt;&#x2F;span&gt;&lt;span&gt; R) -&amp;gt; Grid&amp;lt;TerrainTile&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;const &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;ITEMS_PER_ROOM_DISTRIBUTION&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;usize&lt;&#x2F;span&gt;&lt;span&gt;] &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= &amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;2&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;2&lt;&#x2F;span&gt;&lt;span&gt;];
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;for _ in &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;..&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;NUM_ATTEMPTS &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;if&lt;&#x2F;span&gt;&lt;span&gt; room.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;only_intersects_empty&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;grid) {
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; Add items to the room
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;let &amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;num_items &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;ITEMS_PER_ROOM_DISTRIBUTION&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;choose&lt;&#x2F;span&gt;&lt;span&gt;(rng).&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;unwrap&lt;&#x2F;span&gt;&lt;span&gt;();
&lt;&#x2F;span&gt;&lt;span&gt;            room.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;place_items&lt;&#x2F;span&gt;&lt;span&gt;(num_items, &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut&lt;&#x2F;span&gt;&lt;span&gt; grid, rng);
&lt;&#x2F;span&gt;&lt;span&gt;        }
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;At this point you should be able to pick up fireball scrolls. When you use them the game will
just print the text “todo” to stdout.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;roguelike-tutorial-2020-part-9&#x2F;fireball-scroll.png&quot; alt=&quot;fireball-scroll.png&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Reference implementation branch: &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;gridbugs&#x2F;chargrid-roguelike-tutorial-2020&#x2F;tree&#x2F;part-9.1&quot;&gt;part-9.1&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;h2 id=&quot;launching-fireballs&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#launching-fireballs&quot; aria-label=&quot;Anchor link for: launching-fireballs&quot;&gt;Launching Fireballs&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;Now let’s make it possible to shoot fireballs when a fireball scroll is read.
Rather than just teleporting the fireball to its target, let’s animate it moving along its trajectory.&lt;&#x2F;p&gt;
&lt;p&gt;In the previous section we just printed “todo” when a fireball scroll was used.
Instead, we’d like the game to bring up the targeting AI, and when the user selects a target,
shoot a fireball towards it. If the fireball hits a solid object along the way it should stop,
and if the solid object is a character they should take damage.&lt;&#x2F;p&gt;
&lt;p&gt;When a health potion is used it is used immediately, but when a fireball scroll is used we display a UI.
Let’s codify the different ways in which an item can be used:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; world.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;#[derive(Clone, Copy)]
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;pub enum &lt;&#x2F;span&gt;&lt;span&gt;ItemUsage {
&lt;&#x2F;span&gt;&lt;span&gt;    Immediate,
&lt;&#x2F;span&gt;&lt;span&gt;    Aim,
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Update item-usage methods to return the &lt;code&gt;ItemUsage&lt;&#x2F;code&gt; of the item being used.
Previously we made the assumption that when an item is used, it is immediately
removed from the inventory, but this is only true for items whose usage is &lt;code&gt;Immediate&lt;&#x2F;code&gt;.
Update &lt;code&gt;maybe_use_item&lt;&#x2F;code&gt; to reflect this while we’re at it.
We’ll need to implement &lt;code&gt;Inventory::get&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; world.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;impl &lt;&#x2F;span&gt;&lt;span&gt;Inventory {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;pub fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;get&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;self, index: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;usize&lt;&#x2F;span&gt;&lt;span&gt;) -&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Result&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;Entity, InventorySlotIsEmpty&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;        self.slots
&lt;&#x2F;span&gt;&lt;span&gt;            .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;get&lt;&#x2F;span&gt;&lt;span&gt;(index)
&lt;&#x2F;span&gt;&lt;span&gt;            .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;cloned&lt;&#x2F;span&gt;&lt;span&gt;()
&lt;&#x2F;span&gt;&lt;span&gt;            .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;flatten&lt;&#x2F;span&gt;&lt;span&gt;()
&lt;&#x2F;span&gt;&lt;span&gt;            .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;ok_or&lt;&#x2F;span&gt;&lt;span&gt;(InventorySlotIsEmpty)
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;impl &lt;&#x2F;span&gt;&lt;span&gt;World {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;pub fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;maybe_use_item&lt;&#x2F;span&gt;&lt;span&gt;(
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut &lt;&#x2F;span&gt;&lt;span&gt;self,
&lt;&#x2F;span&gt;&lt;span&gt;        character: Entity,
&lt;&#x2F;span&gt;&lt;span&gt;        inventory_index: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;usize&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;        message_log: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Vec&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;LogMessage&amp;gt;,
&lt;&#x2F;span&gt;&lt;span&gt;    ) -&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Result&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;ItemUsage, ()&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; inventory &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span&gt;self
&lt;&#x2F;span&gt;&lt;span&gt;            .components
&lt;&#x2F;span&gt;&lt;span&gt;            .inventory
&lt;&#x2F;span&gt;&lt;span&gt;            .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;get_mut&lt;&#x2F;span&gt;&lt;span&gt;(character)
&lt;&#x2F;span&gt;&lt;span&gt;            .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;expect&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;character has no inventory&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;);
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; item &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= match&lt;&#x2F;span&gt;&lt;span&gt; inventory.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;get&lt;&#x2F;span&gt;&lt;span&gt;(inventory_index) {
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Ok&lt;&#x2F;span&gt;&lt;span&gt;(item) &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt; item,
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Err&lt;&#x2F;span&gt;&lt;span&gt;(InventorySlotIsEmpty) &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;                message_log.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;push&lt;&#x2F;span&gt;&lt;span&gt;(LogMessage::NoItemInInventorySlot);
&lt;&#x2F;span&gt;&lt;span&gt;                &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;return &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Err&lt;&#x2F;span&gt;&lt;span&gt;(());
&lt;&#x2F;span&gt;&lt;span&gt;            }
&lt;&#x2F;span&gt;&lt;span&gt;        };
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;let &amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;item_type &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span&gt;self
&lt;&#x2F;span&gt;&lt;span&gt;            .components
&lt;&#x2F;span&gt;&lt;span&gt;            .item
&lt;&#x2F;span&gt;&lt;span&gt;            .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;get&lt;&#x2F;span&gt;&lt;span&gt;(item)
&lt;&#x2F;span&gt;&lt;span&gt;            .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;expect&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;non-item in inventory&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;);
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; usage &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= match&lt;&#x2F;span&gt;&lt;span&gt; item_type {
&lt;&#x2F;span&gt;&lt;span&gt;            ItemType::HealthPotion &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;                &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;let mut&lt;&#x2F;span&gt;&lt;span&gt; hit_points &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span&gt;self
&lt;&#x2F;span&gt;&lt;span&gt;                    .components
&lt;&#x2F;span&gt;&lt;span&gt;                    .hit_points
&lt;&#x2F;span&gt;&lt;span&gt;                    .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;get_mut&lt;&#x2F;span&gt;&lt;span&gt;(character)
&lt;&#x2F;span&gt;&lt;span&gt;                    .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;expect&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;character has no hit points&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;);
&lt;&#x2F;span&gt;&lt;span&gt;                &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;const &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;HEALTH_TO_HEAL&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;u32 = &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;5&lt;&#x2F;span&gt;&lt;span&gt;;
&lt;&#x2F;span&gt;&lt;span&gt;                hit_points.current &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; hit_points.max.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;min&lt;&#x2F;span&gt;&lt;span&gt;(hit_points.current &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;+ &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;HEALTH_TO_HEAL&lt;&#x2F;span&gt;&lt;span&gt;);
&lt;&#x2F;span&gt;&lt;span&gt;                inventory.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;remove&lt;&#x2F;span&gt;&lt;span&gt;(inventory_index).&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;unwrap&lt;&#x2F;span&gt;&lt;span&gt;();
&lt;&#x2F;span&gt;&lt;span&gt;                message_log.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;push&lt;&#x2F;span&gt;&lt;span&gt;(LogMessage::PlayerHeals);
&lt;&#x2F;span&gt;&lt;span&gt;                ItemUsage::Immediate
&lt;&#x2F;span&gt;&lt;span&gt;            }
&lt;&#x2F;span&gt;&lt;span&gt;            ItemType::FireballScroll &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span&gt;ItemUsage::Aim,
&lt;&#x2F;span&gt;&lt;span&gt;        };
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Ok&lt;&#x2F;span&gt;&lt;span&gt;(usage)
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; game.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;use crate&lt;&#x2F;span&gt;&lt;span&gt;::world::{
&lt;&#x2F;span&gt;&lt;span&gt;    HitPoints, Inventory, ItemType, ItemUsage, Location, NpcType, Populate, ProjectileType, Tile,
&lt;&#x2F;span&gt;&lt;span&gt;    World,
&lt;&#x2F;span&gt;&lt;span&gt;};
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;impl &lt;&#x2F;span&gt;&lt;span&gt;GameState {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;pub fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;maybe_player_use_item&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut &lt;&#x2F;span&gt;&lt;span&gt;self, inventory_index: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;usize&lt;&#x2F;span&gt;&lt;span&gt;) -&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Result&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;ItemUsage, ()&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; result &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=
&lt;&#x2F;span&gt;&lt;span&gt;            self.world
&lt;&#x2F;span&gt;&lt;span&gt;                .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;maybe_use_item&lt;&#x2F;span&gt;&lt;span&gt;(self.player_entity, inventory_index, &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut &lt;&#x2F;span&gt;&lt;span&gt;self.message_log);
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;if let &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Ok&lt;&#x2F;span&gt;&lt;span&gt;(usage) &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; result {
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;match&lt;&#x2F;span&gt;&lt;span&gt; usage {
&lt;&#x2F;span&gt;&lt;span&gt;                ItemUsage::Immediate &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span&gt;self.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;ai_turn&lt;&#x2F;span&gt;&lt;span&gt;(),
&lt;&#x2F;span&gt;&lt;span&gt;                ItemUsage::Aim &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span&gt;(),
&lt;&#x2F;span&gt;&lt;span&gt;            }
&lt;&#x2F;span&gt;&lt;span&gt;        }
&lt;&#x2F;span&gt;&lt;span&gt;        result
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Update the &lt;code&gt;use_item()&lt;&#x2F;code&gt; &lt;code&gt;EventRoutine&lt;&#x2F;code&gt; to invoke the target &lt;code&gt;EventRoutine&lt;&#x2F;code&gt; when the player uses an item
whose usage is &lt;code&gt;Aim&lt;&#x2F;code&gt;. Note the not-yet-implemented &lt;code&gt;GameState::maybe_player_use_item_aim&lt;&#x2F;code&gt; being called here,
which will actually launch the fireball.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; app.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;use &lt;&#x2F;span&gt;&lt;span&gt;chargrid::{
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;    event_routine::{
&lt;&#x2F;span&gt;&lt;span&gt;        self,
&lt;&#x2F;span&gt;&lt;span&gt;        common_event::{CommonEvent, Delay},
&lt;&#x2F;span&gt;&lt;span&gt;        make_either, DataSelector, Decorate, EventOrPeek, EventRoutine, EventRoutineView, Handled,
&lt;&#x2F;span&gt;&lt;span&gt;        Loop, SideEffect, SideEffectThen, Value, ViewSelector,
&lt;&#x2F;span&gt;&lt;span&gt;    },
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;};
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;use_item&lt;&#x2F;span&gt;&lt;span&gt;() -&amp;gt; impl EventRoutine&amp;lt;Return = (), Data = AppData, View = AppView, Event = CommonEvent&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;    make_either!(Ei &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; A &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;|&lt;&#x2F;span&gt;&lt;span&gt; B);
&lt;&#x2F;span&gt;&lt;span&gt;    Loop::new(|| {
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;inventory_slot_menu&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;Use Item&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;).&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;and_then&lt;&#x2F;span&gt;&lt;span&gt;(|result| &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;match&lt;&#x2F;span&gt;&lt;span&gt; result {
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Err&lt;&#x2F;span&gt;&lt;span&gt;(menu::Escape) &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span&gt;Ei::A(Value::new(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Some&lt;&#x2F;span&gt;&lt;span&gt;(()))),
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Ok&lt;&#x2F;span&gt;&lt;span&gt;(entry) &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span&gt;Ei::B(SideEffectThen::new_with_view(
&lt;&#x2F;span&gt;&lt;span&gt;                &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;move |&lt;&#x2F;span&gt;&lt;span&gt;data: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut&lt;&#x2F;span&gt;&lt;span&gt; AppData, &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;_&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;_| &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;                    make_either!(Ei &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; A &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;|&lt;&#x2F;span&gt;&lt;span&gt; B &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;|&lt;&#x2F;span&gt;&lt;span&gt; C);
&lt;&#x2F;span&gt;&lt;span&gt;                    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;if let &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Ok&lt;&#x2F;span&gt;&lt;span&gt;(usage) &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; data.game_state.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;maybe_player_use_item&lt;&#x2F;span&gt;&lt;span&gt;(entry.index) {
&lt;&#x2F;span&gt;&lt;span&gt;                        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;match&lt;&#x2F;span&gt;&lt;span&gt; usage {
&lt;&#x2F;span&gt;&lt;span&gt;                            ItemUsage::Immediate &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span&gt;Ei::A(Value::new(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Some&lt;&#x2F;span&gt;&lt;span&gt;(()))),
&lt;&#x2F;span&gt;&lt;span&gt;                            ItemUsage::Aim &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span&gt;Ei::B(TargetEventRoutine { name: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;AIM&amp;quot; &lt;&#x2F;span&gt;&lt;span&gt;}.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;and_then&lt;&#x2F;span&gt;&lt;span&gt;(
&lt;&#x2F;span&gt;&lt;span&gt;                                &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;move |&lt;&#x2F;span&gt;&lt;span&gt;maybe_coord&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;| &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;                                    SideEffect::new_with_view(&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;move |&lt;&#x2F;span&gt;&lt;span&gt;data: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut&lt;&#x2F;span&gt;&lt;span&gt; AppData, &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;_&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;_| &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;                                        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;if let &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Some&lt;&#x2F;span&gt;&lt;span&gt;(coord) &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; maybe_coord {
&lt;&#x2F;span&gt;&lt;span&gt;                                            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;if&lt;&#x2F;span&gt;&lt;span&gt; data
&lt;&#x2F;span&gt;&lt;span&gt;                                                .game_state
&lt;&#x2F;span&gt;&lt;span&gt;                                                .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;maybe_player_use_item_aim&lt;&#x2F;span&gt;&lt;span&gt;(entry.index, coord)
&lt;&#x2F;span&gt;&lt;span&gt;                                                .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;is_ok&lt;&#x2F;span&gt;&lt;span&gt;()
&lt;&#x2F;span&gt;&lt;span&gt;                                            {
&lt;&#x2F;span&gt;&lt;span&gt;                                                &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Some&lt;&#x2F;span&gt;&lt;span&gt;(())
&lt;&#x2F;span&gt;&lt;span&gt;                                            } &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;else &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;                                                &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;None
&lt;&#x2F;span&gt;&lt;span&gt;                                            }
&lt;&#x2F;span&gt;&lt;span&gt;                                        } &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;else &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;                                            &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;None
&lt;&#x2F;span&gt;&lt;span&gt;                                        }
&lt;&#x2F;span&gt;&lt;span&gt;                                    })
&lt;&#x2F;span&gt;&lt;span&gt;                                },
&lt;&#x2F;span&gt;&lt;span&gt;                            )),
&lt;&#x2F;span&gt;&lt;span&gt;                        }
&lt;&#x2F;span&gt;&lt;span&gt;                    } &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;else &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;                        Ei::C(Value::new(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;None&lt;&#x2F;span&gt;&lt;span&gt;))
&lt;&#x2F;span&gt;&lt;span&gt;                    }
&lt;&#x2F;span&gt;&lt;span&gt;                },
&lt;&#x2F;span&gt;&lt;span&gt;            )),
&lt;&#x2F;span&gt;&lt;span&gt;        })
&lt;&#x2F;span&gt;&lt;span&gt;    })
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Implement &lt;code&gt;GameState::maybe_player_use_item_aim&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; game.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;impl &lt;&#x2F;span&gt;&lt;span&gt;GameState {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;pub fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;maybe_player_use_item_aim&lt;&#x2F;span&gt;&lt;span&gt;(
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut &lt;&#x2F;span&gt;&lt;span&gt;self,
&lt;&#x2F;span&gt;&lt;span&gt;        inventory_index: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;usize&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;        target: Coord,
&lt;&#x2F;span&gt;&lt;span&gt;    ) -&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Result&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;(), ()&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;        self.world.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;maybe_use_item_aim&lt;&#x2F;span&gt;&lt;span&gt;(
&lt;&#x2F;span&gt;&lt;span&gt;            self.player_entity,
&lt;&#x2F;span&gt;&lt;span&gt;            inventory_index,
&lt;&#x2F;span&gt;&lt;span&gt;            target,
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut &lt;&#x2F;span&gt;&lt;span&gt;self.message_log,
&lt;&#x2F;span&gt;&lt;span&gt;        )
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;And implement &lt;code&gt;World::maybe_use_item_aim&lt;&#x2F;code&gt;. This function assumes it’s called on a sensible
item (e.g. you don’t try to aim a health potion). The game is implemented such that it should
be impossible to call this method on an invalid item, so this function panics in this case.
Should that panic ever execute, a bug has occurred at some point prior, and we shouldn’t try
to continue running the game.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; world.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;impl &lt;&#x2F;span&gt;&lt;span&gt;World {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;pub fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;maybe_use_item_aim&lt;&#x2F;span&gt;&lt;span&gt;(
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut &lt;&#x2F;span&gt;&lt;span&gt;self,
&lt;&#x2F;span&gt;&lt;span&gt;        character: Entity,
&lt;&#x2F;span&gt;&lt;span&gt;        inventory_index: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;usize&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;        target: Coord,
&lt;&#x2F;span&gt;&lt;span&gt;        message_log: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Vec&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;LogMessage&amp;gt;,
&lt;&#x2F;span&gt;&lt;span&gt;    ) -&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Result&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;(), ()&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; character_coord &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span&gt;self.spatial_table.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;coord_of&lt;&#x2F;span&gt;&lt;span&gt;(character).&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;unwrap&lt;&#x2F;span&gt;&lt;span&gt;();
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;if&lt;&#x2F;span&gt;&lt;span&gt; character_coord &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;==&lt;&#x2F;span&gt;&lt;span&gt; target {
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;return &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Err&lt;&#x2F;span&gt;&lt;span&gt;(());
&lt;&#x2F;span&gt;&lt;span&gt;        }
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; inventory &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span&gt;self
&lt;&#x2F;span&gt;&lt;span&gt;            .components
&lt;&#x2F;span&gt;&lt;span&gt;            .inventory
&lt;&#x2F;span&gt;&lt;span&gt;            .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;get_mut&lt;&#x2F;span&gt;&lt;span&gt;(character)
&lt;&#x2F;span&gt;&lt;span&gt;            .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;expect&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;character has no inventory&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;);
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; item_entity &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; inventory.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;remove&lt;&#x2F;span&gt;&lt;span&gt;(inventory_index).&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;unwrap&lt;&#x2F;span&gt;&lt;span&gt;();
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;let &amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;item_type &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span&gt;self.components.item.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;get&lt;&#x2F;span&gt;&lt;span&gt;(item_entity).&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;unwrap&lt;&#x2F;span&gt;&lt;span&gt;();
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;match&lt;&#x2F;span&gt;&lt;span&gt; item_type {
&lt;&#x2F;span&gt;&lt;span&gt;            ItemType::HealthPotion &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span&gt;panic!(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;invalid item for aim&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;),
&lt;&#x2F;span&gt;&lt;span&gt;            ItemType::FireballScroll &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;                message_log.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;push&lt;&#x2F;span&gt;&lt;span&gt;(LogMessage::PlayerLaunchesProjectile(
&lt;&#x2F;span&gt;&lt;span&gt;                    ProjectileType::Fireball,
&lt;&#x2F;span&gt;&lt;span&gt;                ));
&lt;&#x2F;span&gt;&lt;span&gt;                self.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;spawn_projectile&lt;&#x2F;span&gt;&lt;span&gt;(character_coord, target, ProjectileType::Fireball);
&lt;&#x2F;span&gt;&lt;span&gt;            }
&lt;&#x2F;span&gt;&lt;span&gt;        }
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Ok&lt;&#x2F;span&gt;&lt;span&gt;(())
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Two things in the above code haven’t been defined yet:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;the &lt;code&gt;LogMessage::PlayerLaunchesProjectile&lt;&#x2F;code&gt; variant&lt;&#x2F;li&gt;
&lt;li&gt;the &lt;code&gt;World::spawn_projectile&lt;&#x2F;code&gt; method&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Add the log message types.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; game.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;use crate&lt;&#x2F;span&gt;&lt;span&gt;::world::{
&lt;&#x2F;span&gt;&lt;span&gt;    HitPoints, Inventory, ItemType, ItemUsage, Location, NpcType, Populate, ProjectileType, Tile,
&lt;&#x2F;span&gt;&lt;span&gt;    World,
&lt;&#x2F;span&gt;&lt;span&gt;};
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;pub enum &lt;&#x2F;span&gt;&lt;span&gt;LogMessage {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;    PlayerLaunchesProjectile(ProjectileType),
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This depends on a new type &lt;code&gt;ProjectileType&lt;&#x2F;code&gt;. Add it to &lt;code&gt;world.rs&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; world.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;#[derive(Clone, Copy, Debug)]
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;pub enum &lt;&#x2F;span&gt;&lt;span&gt;ProjectileType {
&lt;&#x2F;span&gt;&lt;span&gt;    Fireball,
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;impl &lt;&#x2F;span&gt;&lt;span&gt;ProjectileType {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;pub fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;name&lt;&#x2F;span&gt;&lt;span&gt;(self) -&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&amp;#39;static str &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;match &lt;&#x2F;span&gt;&lt;span&gt;self {
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;Self&lt;&#x2F;span&gt;&lt;span&gt;::Fireball &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;fireball&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;        }
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Handle the new type of log message.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; ui.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;impl&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;#39;a&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt; View&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&amp;#39;a &lt;&#x2F;span&gt;&lt;span&gt;[LogMessage]&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;for &lt;&#x2F;span&gt;&lt;span&gt;MessagesView {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;view&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;F: Frame, C: ColModify&amp;gt;(
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut &lt;&#x2F;span&gt;&lt;span&gt;self,
&lt;&#x2F;span&gt;&lt;span&gt;        messages: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&amp;#39;a&lt;&#x2F;span&gt;&lt;span&gt; [LogMessage],
&lt;&#x2F;span&gt;&lt;span&gt;        context: ViewContext&amp;lt;C&amp;gt;,
&lt;&#x2F;span&gt;&lt;span&gt;        frame: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut&lt;&#x2F;span&gt;&lt;span&gt; F,
&lt;&#x2F;span&gt;&lt;span&gt;    ) {
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;format_message&lt;&#x2F;span&gt;&lt;span&gt;(buf: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut&lt;&#x2F;span&gt;&lt;span&gt; [RichTextPartOwned], message: LogMessage) {
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;match&lt;&#x2F;span&gt;&lt;span&gt; message {
&lt;&#x2F;span&gt;&lt;span&gt;                &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;                PlayerLaunchesProjectile(projectile) &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;                    write!(&amp;amp;mut buf[0].text, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;You launch a &amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;).&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;unwrap&lt;&#x2F;span&gt;&lt;span&gt;();
&lt;&#x2F;span&gt;&lt;span&gt;                    write!(&amp;amp;mut buf[1].text, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;{}&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;, projectile.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;name&lt;&#x2F;span&gt;&lt;span&gt;()).&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;unwrap&lt;&#x2F;span&gt;&lt;span&gt;();
&lt;&#x2F;span&gt;&lt;span&gt;                    buf[&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;].style.foreground &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Some&lt;&#x2F;span&gt;&lt;span&gt;(colours::projectile_colour(projectile));
&lt;&#x2F;span&gt;&lt;span&gt;                    write!(&amp;amp;mut buf[2].text, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;!&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;).&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;unwrap&lt;&#x2F;span&gt;&lt;span&gt;();
&lt;&#x2F;span&gt;&lt;span&gt;                }
&lt;&#x2F;span&gt;&lt;span&gt;            }
&lt;&#x2F;span&gt;&lt;span&gt;        }
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This depends on &lt;code&gt;colour::projectile_colour&lt;&#x2F;code&gt;. Define it.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; app.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;use crate&lt;&#x2F;span&gt;&lt;span&gt;::world::{ItemType, ItemUsage, Layer, NpcType, ProjectileType, Tile};
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;pub mod &lt;&#x2F;span&gt;&lt;span&gt;colours {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;pub fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;projectile_colour&lt;&#x2F;span&gt;&lt;span&gt;(projcetile_type: ProjectileType) -&amp;gt; Rgb24 {
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;match&lt;&#x2F;span&gt;&lt;span&gt; projcetile_type {
&lt;&#x2F;span&gt;&lt;span&gt;            ProjectileType::Fireball &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;FIREBALL_SCROLL&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;        }
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Jumping all over the codebase today.&lt;&#x2F;p&gt;
&lt;p&gt;Back in &lt;code&gt;world.rs&lt;&#x2F;code&gt;, define the &lt;code&gt;spawn_projectile&lt;&#x2F;code&gt; method.
Add a &lt;code&gt;projectile&lt;&#x2F;code&gt; component storing a &lt;code&gt;ProjectileType&lt;&#x2F;code&gt;, a &lt;code&gt;Projectile&lt;&#x2F;code&gt; tile, and a &lt;code&gt;projcetile&lt;&#x2F;code&gt; layer.
Also add a &lt;code&gt;trajectory&lt;&#x2F;code&gt; component for storing the motion path of a projectile.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; world.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;use &lt;&#x2F;span&gt;&lt;span&gt;line_2d::CardinalStepIter;
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;pub enum &lt;&#x2F;span&gt;&lt;span&gt;Tile {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;    Projectile(ProjectileType),
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;entity_table::declare_entity_module&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;! &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;    components {
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;        trajectory: CardinalStepIter,
&lt;&#x2F;span&gt;&lt;span&gt;        projectile: ProjectileType,
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;spatial_table::declare_layers_module&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;! &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;    layers {
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;        projectile: Projectile,
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;impl &lt;&#x2F;span&gt;&lt;span&gt;World {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;spawn_projectile&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut &lt;&#x2F;span&gt;&lt;span&gt;self, from: Coord, to: Coord, projectile_type: ProjectileType) {
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; entity &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span&gt;self.entity_allocator.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;alloc&lt;&#x2F;span&gt;&lt;span&gt;();
&lt;&#x2F;span&gt;&lt;span&gt;        self.spatial_table
&lt;&#x2F;span&gt;&lt;span&gt;            .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;update&lt;&#x2F;span&gt;&lt;span&gt;(
&lt;&#x2F;span&gt;&lt;span&gt;                entity,
&lt;&#x2F;span&gt;&lt;span&gt;                Location {
&lt;&#x2F;span&gt;&lt;span&gt;                    coord: from,
&lt;&#x2F;span&gt;&lt;span&gt;                    layer: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Some&lt;&#x2F;span&gt;&lt;span&gt;(Layer::Projectile),
&lt;&#x2F;span&gt;&lt;span&gt;                },
&lt;&#x2F;span&gt;&lt;span&gt;            )
&lt;&#x2F;span&gt;&lt;span&gt;            .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;unwrap&lt;&#x2F;span&gt;&lt;span&gt;();
&lt;&#x2F;span&gt;&lt;span&gt;        self.components
&lt;&#x2F;span&gt;&lt;span&gt;            .tile
&lt;&#x2F;span&gt;&lt;span&gt;            .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;insert&lt;&#x2F;span&gt;&lt;span&gt;(entity, Tile::Projectile(projectile_type));
&lt;&#x2F;span&gt;&lt;span&gt;        self.components.projectile.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;insert&lt;&#x2F;span&gt;&lt;span&gt;(entity, projectile_type);
&lt;&#x2F;span&gt;&lt;span&gt;        self.components
&lt;&#x2F;span&gt;&lt;span&gt;            .trajectory
&lt;&#x2F;span&gt;&lt;span&gt;            .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;insert&lt;&#x2F;span&gt;&lt;span&gt;(entity, CardinalStepIter::new(to &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;-&lt;&#x2F;span&gt;&lt;span&gt; from));
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Note the &lt;code&gt;CardinalStepIter&lt;&#x2F;code&gt; type. This is an iterator over the coordinates along
a line segment between 2 points, only taking steps in cardinal directions.
We’ll use it to compute the path followed by a projectile.&lt;&#x2F;p&gt;
&lt;p&gt;Handle the new tile type and new layer in &lt;code&gt;app.rs&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; app.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;impl&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;#39;a&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt; View&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&amp;#39;a&lt;&#x2F;span&gt;&lt;span&gt; GameState&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;for &lt;&#x2F;span&gt;&lt;span&gt;GameView {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;view&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;F: Frame, C: ColModify&amp;gt;(
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut &lt;&#x2F;span&gt;&lt;span&gt;self,
&lt;&#x2F;span&gt;&lt;span&gt;        game_state: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&amp;#39;a&lt;&#x2F;span&gt;&lt;span&gt; GameState,
&lt;&#x2F;span&gt;&lt;span&gt;        context: ViewContext&amp;lt;C&amp;gt;,
&lt;&#x2F;span&gt;&lt;span&gt;        frame: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut&lt;&#x2F;span&gt;&lt;span&gt; F,
&lt;&#x2F;span&gt;&lt;span&gt;    ) {
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;for&lt;&#x2F;span&gt;&lt;span&gt; entity_to_render &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;in&lt;&#x2F;span&gt;&lt;span&gt; game_state.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;entities_to_render&lt;&#x2F;span&gt;&lt;span&gt;() {
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; depth &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= match&lt;&#x2F;span&gt;&lt;span&gt; entity_to_render.location.layer {
&lt;&#x2F;span&gt;&lt;span&gt;                &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;                &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Some&lt;&#x2F;span&gt;&lt;span&gt;(Layer::Projectile) &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;4&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;            };
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;        }
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;currently_visible_view_cell_of_tile&lt;&#x2F;span&gt;&lt;span&gt;(tile: Tile) -&amp;gt; ViewCell {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;match&lt;&#x2F;span&gt;&lt;span&gt; tile {
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;        Tile::Projectile(ProjectileType::Fireball) &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span&gt;ViewCell::new()
&lt;&#x2F;span&gt;&lt;span&gt;            .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;with_character&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;#39;*&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;)
&lt;&#x2F;span&gt;&lt;span&gt;            .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;with_foreground&lt;&#x2F;span&gt;&lt;span&gt;(colours::&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;FIREBALL_SCROLL&lt;&#x2F;span&gt;&lt;span&gt;),
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Add a method to &lt;code&gt;World&lt;&#x2F;code&gt; for moving all projectiles one step along their motion path.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; world.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;impl &lt;&#x2F;span&gt;&lt;span&gt;World {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;pub fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;move_projectiles&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut &lt;&#x2F;span&gt;&lt;span&gt;self, message_log: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Vec&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;LogMessage&amp;gt;) {
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;let mut&lt;&#x2F;span&gt;&lt;span&gt; entities_to_remove &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Vec&lt;&#x2F;span&gt;&lt;span&gt;::new();
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;let mut&lt;&#x2F;span&gt;&lt;span&gt; fireball_hit &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Vec&lt;&#x2F;span&gt;&lt;span&gt;::new();
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;for &lt;&#x2F;span&gt;&lt;span&gt;(entity, trajectory) &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;in &lt;&#x2F;span&gt;&lt;span&gt;self.components.trajectory.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;iter_mut&lt;&#x2F;span&gt;&lt;span&gt;() {
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;if let &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Some&lt;&#x2F;span&gt;&lt;span&gt;(direction) &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; trajectory.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;next&lt;&#x2F;span&gt;&lt;span&gt;() {
&lt;&#x2F;span&gt;&lt;span&gt;                &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; current_coord &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span&gt;self.spatial_table.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;coord_of&lt;&#x2F;span&gt;&lt;span&gt;(entity).&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;unwrap&lt;&#x2F;span&gt;&lt;span&gt;();
&lt;&#x2F;span&gt;&lt;span&gt;                &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; new_coord &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; current_coord &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;+&lt;&#x2F;span&gt;&lt;span&gt; direction.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;coord&lt;&#x2F;span&gt;&lt;span&gt;();
&lt;&#x2F;span&gt;&lt;span&gt;                &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; dest_layers &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span&gt;self.spatial_table.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;layers_at_checked&lt;&#x2F;span&gt;&lt;span&gt;(new_coord);
&lt;&#x2F;span&gt;&lt;span&gt;                &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;if&lt;&#x2F;span&gt;&lt;span&gt; dest_layers.feature.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;is_some&lt;&#x2F;span&gt;&lt;span&gt;() {
&lt;&#x2F;span&gt;&lt;span&gt;                    entities_to_remove.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;push&lt;&#x2F;span&gt;&lt;span&gt;(entity);
&lt;&#x2F;span&gt;&lt;span&gt;                } &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;else if let &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Some&lt;&#x2F;span&gt;&lt;span&gt;(character) &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; dest_layers.character {
&lt;&#x2F;span&gt;&lt;span&gt;                    entities_to_remove.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;push&lt;&#x2F;span&gt;&lt;span&gt;(entity);
&lt;&#x2F;span&gt;&lt;span&gt;                    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;if let &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Some&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;projectile_type) &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span&gt;self.components.projectile.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;get&lt;&#x2F;span&gt;&lt;span&gt;(entity) {
&lt;&#x2F;span&gt;&lt;span&gt;                        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;match&lt;&#x2F;span&gt;&lt;span&gt; projectile_type {
&lt;&#x2F;span&gt;&lt;span&gt;                            ProjectileType::Fireball &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;                                fireball_hit.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;push&lt;&#x2F;span&gt;&lt;span&gt;(character);
&lt;&#x2F;span&gt;&lt;span&gt;                            }
&lt;&#x2F;span&gt;&lt;span&gt;                        }
&lt;&#x2F;span&gt;&lt;span&gt;                    }
&lt;&#x2F;span&gt;&lt;span&gt;                }
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;                &lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; ignore collisiosns of projectiles
&lt;&#x2F;span&gt;&lt;span&gt;                &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;let _ = &lt;&#x2F;span&gt;&lt;span&gt;self.spatial_table.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;update_coord&lt;&#x2F;span&gt;&lt;span&gt;(entity, new_coord);
&lt;&#x2F;span&gt;&lt;span&gt;            } &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;else &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;                entities_to_remove.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;push&lt;&#x2F;span&gt;&lt;span&gt;(entity);
&lt;&#x2F;span&gt;&lt;span&gt;            }
&lt;&#x2F;span&gt;&lt;span&gt;        }
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;for&lt;&#x2F;span&gt;&lt;span&gt; entity &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;in&lt;&#x2F;span&gt;&lt;span&gt; entities_to_remove {
&lt;&#x2F;span&gt;&lt;span&gt;            self.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;remove_entity&lt;&#x2F;span&gt;&lt;span&gt;(entity);
&lt;&#x2F;span&gt;&lt;span&gt;        }
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;for&lt;&#x2F;span&gt;&lt;span&gt; entity &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;in&lt;&#x2F;span&gt;&lt;span&gt; fireball_hit {
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; maybe_npc &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span&gt;self.components.npc_type.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;get&lt;&#x2F;span&gt;&lt;span&gt;(entity).&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;cloned&lt;&#x2F;span&gt;&lt;span&gt;();
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;if let &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Some&lt;&#x2F;span&gt;&lt;span&gt;(VictimDies) &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span&gt;self.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;character_damage&lt;&#x2F;span&gt;&lt;span&gt;(entity, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;2&lt;&#x2F;span&gt;&lt;span&gt;) {
&lt;&#x2F;span&gt;&lt;span&gt;                &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;if let &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Some&lt;&#x2F;span&gt;&lt;span&gt;(npc) &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; maybe_npc {
&lt;&#x2F;span&gt;&lt;span&gt;                    message_log.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;push&lt;&#x2F;span&gt;&lt;span&gt;(LogMessage::NpcDies(npc));
&lt;&#x2F;span&gt;&lt;span&gt;                }
&lt;&#x2F;span&gt;&lt;span&gt;            }
&lt;&#x2F;span&gt;&lt;span&gt;        }
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This requires some generalizations of our combat logic,
in particular adding a &lt;code&gt;character_damage&lt;&#x2F;code&gt; method, extracting this logic from &lt;code&gt;character_bump_attack&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; world.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;impl &lt;&#x2F;span&gt;&lt;span&gt;World {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;character_bump_attack&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut &lt;&#x2F;span&gt;&lt;span&gt;self, victim: Entity) -&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Option&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;VictimDies&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;        self.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;character_damage&lt;&#x2F;span&gt;&lt;span&gt;(victim, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;)
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;character_damage&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut &lt;&#x2F;span&gt;&lt;span&gt;self, victim: Entity, damage: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;u32&lt;&#x2F;span&gt;&lt;span&gt;) -&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Option&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;VictimDies&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;if let &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Some&lt;&#x2F;span&gt;&lt;span&gt;(hit_points) &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span&gt;self.components.hit_points.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;get_mut&lt;&#x2F;span&gt;&lt;span&gt;(victim) {
&lt;&#x2F;span&gt;&lt;span&gt;            hit_points.current &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; hit_points.current.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;saturating_sub&lt;&#x2F;span&gt;&lt;span&gt;(damage);
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;if&lt;&#x2F;span&gt;&lt;span&gt; hit_points.current &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;== &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;0 &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;                self.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;character_die&lt;&#x2F;span&gt;&lt;span&gt;(victim);
&lt;&#x2F;span&gt;&lt;span&gt;                &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;return &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Some&lt;&#x2F;span&gt;&lt;span&gt;(VictimDies);
&lt;&#x2F;span&gt;&lt;span&gt;            }
&lt;&#x2F;span&gt;&lt;span&gt;        }
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;None
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;If an NPC is killed by a fireball, a new log message &lt;code&gt;NpcDies&lt;&#x2F;code&gt; is generated.
Add it.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; game.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;pub enum &lt;&#x2F;span&gt;&lt;span&gt;LogMessage {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;    NpcDies(NpcType),
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;And handle it.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; ui.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;impl&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;#39;a&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt; View&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&amp;#39;a &lt;&#x2F;span&gt;&lt;span&gt;[LogMessage]&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;for &lt;&#x2F;span&gt;&lt;span&gt;MessagesView {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;view&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;F: Frame, C: ColModify&amp;gt;(
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut &lt;&#x2F;span&gt;&lt;span&gt;self,
&lt;&#x2F;span&gt;&lt;span&gt;        messages: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&amp;#39;a&lt;&#x2F;span&gt;&lt;span&gt; [LogMessage],
&lt;&#x2F;span&gt;&lt;span&gt;        context: ViewContext&amp;lt;C&amp;gt;,
&lt;&#x2F;span&gt;&lt;span&gt;        frame: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut&lt;&#x2F;span&gt;&lt;span&gt; F,
&lt;&#x2F;span&gt;&lt;span&gt;    ) {
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;format_message&lt;&#x2F;span&gt;&lt;span&gt;(buf: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut&lt;&#x2F;span&gt;&lt;span&gt; [RichTextPartOwned], message: LogMessage) {
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;match&lt;&#x2F;span&gt;&lt;span&gt; message {
&lt;&#x2F;span&gt;&lt;span&gt;                &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;                NpcDies(npc_type) &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;                    write!(&amp;amp;mut buf[0].text, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;The &amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;).&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;unwrap&lt;&#x2F;span&gt;&lt;span&gt;();
&lt;&#x2F;span&gt;&lt;span&gt;                    write!(&amp;amp;mut buf[1].text, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;{}&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;, npc_type.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;name&lt;&#x2F;span&gt;&lt;span&gt;()).&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;unwrap&lt;&#x2F;span&gt;&lt;span&gt;();
&lt;&#x2F;span&gt;&lt;span&gt;                    buf[&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;].style.foreground &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Some&lt;&#x2F;span&gt;&lt;span&gt;(colours::npc_colour(npc_type));
&lt;&#x2F;span&gt;&lt;span&gt;                    write!(&amp;amp;mut buf[2].text, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot; dies.&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;).&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;unwrap&lt;&#x2F;span&gt;&lt;span&gt;();
&lt;&#x2F;span&gt;&lt;span&gt;                }
&lt;&#x2F;span&gt;&lt;span&gt;            }
&lt;&#x2F;span&gt;&lt;span&gt;        }
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; world.rs
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Add a method to &lt;code&gt;World&lt;&#x2F;code&gt; for testing whether there are any projectiles.
We’re about to add a simple realtime animation system, and we want an easy way to check
whether any animations are in progress so controls can be ignored while animations are playing.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; world.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;impl &lt;&#x2F;span&gt;&lt;span&gt;World {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;pub fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;has_projectiles&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;self) -&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;bool &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;!&lt;&#x2F;span&gt;&lt;span&gt;self.components.trajectory.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;is_empty&lt;&#x2F;span&gt;&lt;span&gt;()
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Add animation methods to &lt;code&gt;GameState&lt;&#x2F;code&gt;, and prevent the player from acting while animations are in progress.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; game.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;impl &lt;&#x2F;span&gt;&lt;span&gt;GameState {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;pub fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;tick_animations&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut &lt;&#x2F;span&gt;&lt;span&gt;self) {
&lt;&#x2F;span&gt;&lt;span&gt;        self.world.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;move_projectiles&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut &lt;&#x2F;span&gt;&lt;span&gt;self.message_log)
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;has_animations&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;self) -&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;bool &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;        self.world.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;has_projectiles&lt;&#x2F;span&gt;&lt;span&gt;()
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;pub fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;wait_player&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut &lt;&#x2F;span&gt;&lt;span&gt;self) {
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;if &lt;&#x2F;span&gt;&lt;span&gt;self.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;has_animations&lt;&#x2F;span&gt;&lt;span&gt;() {
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;return&lt;&#x2F;span&gt;&lt;span&gt;;
&lt;&#x2F;span&gt;&lt;span&gt;        }
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;pub fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;maybe_move_player&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut &lt;&#x2F;span&gt;&lt;span&gt;self, direction: CardinalDirection) {
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;if &lt;&#x2F;span&gt;&lt;span&gt;self.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;has_animations&lt;&#x2F;span&gt;&lt;span&gt;() {
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;return&lt;&#x2F;span&gt;&lt;span&gt;;
&lt;&#x2F;span&gt;&lt;span&gt;        }
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;pub fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;maybe_player_get_item&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut &lt;&#x2F;span&gt;&lt;span&gt;self) {
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;if &lt;&#x2F;span&gt;&lt;span&gt;self.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;has_animations&lt;&#x2F;span&gt;&lt;span&gt;() {
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;return&lt;&#x2F;span&gt;&lt;span&gt;;
&lt;&#x2F;span&gt;&lt;span&gt;        }
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;pub fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;maybe_player_use_item&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut &lt;&#x2F;span&gt;&lt;span&gt;self, inventory_index: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;usize&lt;&#x2F;span&gt;&lt;span&gt;) -&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Result&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;ItemUsage, ()&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;if &lt;&#x2F;span&gt;&lt;span&gt;self.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;has_animations&lt;&#x2F;span&gt;&lt;span&gt;() {
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;return &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Err&lt;&#x2F;span&gt;&lt;span&gt;(());
&lt;&#x2F;span&gt;&lt;span&gt;        }
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Now we need to periodically tick animations by calling &lt;code&gt;GameState::tick_animations&lt;&#x2F;code&gt;.
Let’s only progress animations during normal gameplay, at a rate of 30 FPS (regardless of the game’s actual framerate).
Since the game is likely running at a higher framerate, we need to keep track of the passage of time, and only progress
animations very 33ms. Game ticks are sent to &lt;code&gt;EventRoutine&lt;&#x2F;code&gt;s in the form of &lt;code&gt;CommonEvent::Frame(period)&lt;&#x2F;code&gt; events,
where &lt;code&gt;period&lt;&#x2F;code&gt; is a &lt;code&gt;std::time::Duration&lt;&#x2F;code&gt; containing the amount of time that has passed since the previous frame.
Ticks are generally synchronized to the display’s framerate, but this is not a necessity and you shouldn’t rely on it.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; app.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;const &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;BETWEEN_ANIMATION_TICKS&lt;&#x2F;span&gt;&lt;span&gt;: Duration &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span&gt;Duration::from_millis(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;33&lt;&#x2F;span&gt;&lt;span&gt;);
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;struct &lt;&#x2F;span&gt;&lt;span&gt;AppData {
&lt;&#x2F;span&gt;&lt;span&gt;    ...
&lt;&#x2F;span&gt;&lt;span&gt;    until_next_animation_tick: Duration,
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;impl &lt;&#x2F;span&gt;&lt;span&gt;AppData {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;new&lt;&#x2F;span&gt;&lt;span&gt;(screen_size: Size, rng_seed: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;u64&lt;&#x2F;span&gt;&lt;span&gt;, visibility_algorithm: VisibilityAlgorithm) -&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;Self &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;Self &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;            until_next_animation_tick: Duration::from_millis(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt;),
&lt;&#x2F;span&gt;&lt;span&gt;        }
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;impl &lt;&#x2F;span&gt;&lt;span&gt;EventRoutine &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;for &lt;&#x2F;span&gt;&lt;span&gt;GameEventRoutine {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;type &lt;&#x2F;span&gt;&lt;span&gt;Return &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; GameReturn;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;type &lt;&#x2F;span&gt;&lt;span&gt;Data &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; AppData;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;type &lt;&#x2F;span&gt;&lt;span&gt;View &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; AppView;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;type &lt;&#x2F;span&gt;&lt;span&gt;Event &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; CommonEvent;
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;handle&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;EP&amp;gt;(
&lt;&#x2F;span&gt;&lt;span&gt;        self,
&lt;&#x2F;span&gt;&lt;span&gt;        data: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut Self::&lt;&#x2F;span&gt;&lt;span&gt;Data,
&lt;&#x2F;span&gt;&lt;span&gt;        _view: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;Self::&lt;&#x2F;span&gt;&lt;span&gt;View,
&lt;&#x2F;span&gt;&lt;span&gt;        event_or_peek: EP,
&lt;&#x2F;span&gt;&lt;span&gt;    ) -&amp;gt; Handled&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;Self::&lt;&#x2F;span&gt;&lt;span&gt;Return, &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;Self&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;where
&lt;&#x2F;span&gt;&lt;span&gt;        EP: EventOrPeek&amp;lt;Event = &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;Self::&lt;&#x2F;span&gt;&lt;span&gt;Event&amp;gt;,
&lt;&#x2F;span&gt;&lt;span&gt;    {
&lt;&#x2F;span&gt;&lt;span&gt;        event_routine::event_or_peek_with_handled(event_or_peek, self, |s, event| &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;match&lt;&#x2F;span&gt;&lt;span&gt; event {
&lt;&#x2F;span&gt;&lt;span&gt;            CommonEvent::Input(input) &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;                &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;            }
&lt;&#x2F;span&gt;&lt;span&gt;            CommonEvent::Frame(period) &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;                &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;if let &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Some&lt;&#x2F;span&gt;&lt;span&gt;(until_next_animation_tick) &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=
&lt;&#x2F;span&gt;&lt;span&gt;                    data.until_next_animation_tick.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;checked_sub&lt;&#x2F;span&gt;&lt;span&gt;(period)
&lt;&#x2F;span&gt;&lt;span&gt;                {
&lt;&#x2F;span&gt;&lt;span&gt;                    data.until_next_animation_tick &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; until_next_animation_tick;
&lt;&#x2F;span&gt;&lt;span&gt;                } &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;else &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;                    data.until_next_animation_tick &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;BETWEEN_ANIMATION_TICKS&lt;&#x2F;span&gt;&lt;span&gt;;
&lt;&#x2F;span&gt;&lt;span&gt;                    data.game_state.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;tick_animations&lt;&#x2F;span&gt;&lt;span&gt;();
&lt;&#x2F;span&gt;&lt;span&gt;                }
&lt;&#x2F;span&gt;&lt;span&gt;                Handled::Continue(s)
&lt;&#x2F;span&gt;&lt;span&gt;            }
&lt;&#x2F;span&gt;&lt;span&gt;        })
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;And that should do it.
Try picking up a fireball scroll and using it via the inventory menu.
You’ll be presented with an “AIM” target ui.
Target an NPC and hit the enter key or press the left mouse button.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;roguelike-tutorial-2020-part-9&#x2F;aim.png&quot; alt=&quot;aim.png&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;A fireball will appear, and in &lt;em&gt;realtime&lt;&#x2F;em&gt; move towards the NPC.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;roguelike-tutorial-2020-part-9&#x2F;launch.png&quot; alt=&quot;launch.png&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;When it hits them, they’ll take damage and possibly die.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;roguelike-tutorial-2020-part-9&#x2F;hit.png&quot; alt=&quot;hit.png&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Reference implementation branch: &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;gridbugs&#x2F;chargrid-roguelike-tutorial-2020&#x2F;tree&#x2F;part-9.2&quot;&gt;part-9.2&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;h2 id=&quot;confusion-scroll&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#confusion-scroll&quot; aria-label=&quot;Anchor link for: confusion-scroll&quot;&gt;Confusion Scroll&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;Add a &lt;code&gt;ConfusionScroll&lt;&#x2F;code&gt; item, and &lt;code&gt;Confusion&lt;&#x2F;code&gt; projectile.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; world.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;pub enum &lt;&#x2F;span&gt;&lt;span&gt;ProjectileType {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;    Confusion,
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;impl &lt;&#x2F;span&gt;&lt;span&gt;ProjectileType {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;pub fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;name&lt;&#x2F;span&gt;&lt;span&gt;(self) -&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&amp;#39;static str &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;match &lt;&#x2F;span&gt;&lt;span&gt;self {
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;Self&lt;&#x2F;span&gt;&lt;span&gt;::Confusion &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;confusion spell&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;        }
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;pub enum &lt;&#x2F;span&gt;&lt;span&gt;ItemType {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;    ConfusionScroll,
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;impl &lt;&#x2F;span&gt;&lt;span&gt;ItemType {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;pub fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;name&lt;&#x2F;span&gt;&lt;span&gt;(self) -&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&amp;#39;static str &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;match &lt;&#x2F;span&gt;&lt;span&gt;self {
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;Self&lt;&#x2F;span&gt;&lt;span&gt;::ConfusionScroll &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;confusion scroll&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;        }
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Add rendering logic for the new item and projectile.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; app.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;pub mod &lt;&#x2F;span&gt;&lt;span&gt;colours {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;pub const &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;CONFUSION_SCROLL&lt;&#x2F;span&gt;&lt;span&gt;: Rgb24 &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span&gt;Rgb24::new(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;187&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;255&lt;&#x2F;span&gt;&lt;span&gt;);
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;pub fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;item_colour&lt;&#x2F;span&gt;&lt;span&gt;(item_type: ItemType) -&amp;gt; Rgb24 {
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;match&lt;&#x2F;span&gt;&lt;span&gt; item_type {
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;            ItemType::ConfusionScroll &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;CONFUSION_SCROLL&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;        }
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;pub fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;projectile_colour&lt;&#x2F;span&gt;&lt;span&gt;(projcetile_type: ProjectileType) -&amp;gt; Rgb24 {
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;match&lt;&#x2F;span&gt;&lt;span&gt; projcetile_type {
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;            ProjectileType::Confusion &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;CONFUSION_SCROLL&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;        }
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;currently_visible_view_cell_of_tile&lt;&#x2F;span&gt;&lt;span&gt;(tile: Tile) -&amp;gt; ViewCell {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;match&lt;&#x2F;span&gt;&lt;span&gt; tile {
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;        Tile::Item(ItemType::ConfusionScroll) &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span&gt;ViewCell::new()
&lt;&#x2F;span&gt;&lt;span&gt;            .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;with_character&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;#39;♫&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;)
&lt;&#x2F;span&gt;&lt;span&gt;            .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;with_foreground&lt;&#x2F;span&gt;&lt;span&gt;(colours::&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;CONFUSION_SCROLL&lt;&#x2F;span&gt;&lt;span&gt;),
&lt;&#x2F;span&gt;&lt;span&gt;        Tile::Projectile(ProjectileType::Fireball) &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span&gt;ViewCell::new()
&lt;&#x2F;span&gt;&lt;span&gt;            .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;with_character&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;#39;*&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;)
&lt;&#x2F;span&gt;&lt;span&gt;            .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;with_foreground&lt;&#x2F;span&gt;&lt;span&gt;(colours::&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;FIREBALL_SCROLL&lt;&#x2F;span&gt;&lt;span&gt;),
&lt;&#x2F;span&gt;&lt;span&gt;        Tile::Projectile(ProjectileType::Confusion) &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span&gt;ViewCell::new()
&lt;&#x2F;span&gt;&lt;span&gt;            .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;with_character&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;#39;*&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;)
&lt;&#x2F;span&gt;&lt;span&gt;            .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;with_foreground&lt;&#x2F;span&gt;&lt;span&gt;(colours::&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;CONFUSION_SCROLL&lt;&#x2F;span&gt;&lt;span&gt;),
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Place confusion scrolls during dungeon generation. Also rebalance the probabilities of items
such that health potions may appear again.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; terrain.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;impl &lt;&#x2F;span&gt;&lt;span&gt;Room {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; Place `n` items at random positions within the room
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;place_items&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;R: Rng&amp;gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;self, n: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;usize&lt;&#x2F;span&gt;&lt;span&gt;, grid: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut &lt;&#x2F;span&gt;&lt;span&gt;Grid&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Option&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;TerrainTile&amp;gt;&amp;gt;, rng: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut&lt;&#x2F;span&gt;&lt;span&gt; R) {
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;for&lt;&#x2F;span&gt;&lt;span&gt; coord &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;in &lt;&#x2F;span&gt;&lt;span&gt;self
&lt;&#x2F;span&gt;&lt;span&gt;            .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;coords&lt;&#x2F;span&gt;&lt;span&gt;()
&lt;&#x2F;span&gt;&lt;span&gt;            .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;filter&lt;&#x2F;span&gt;&lt;span&gt;(|&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;coord| grid.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;get_checked&lt;&#x2F;span&gt;&lt;span&gt;(coord).&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;unwrap&lt;&#x2F;span&gt;&lt;span&gt;() &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;== &lt;&#x2F;span&gt;&lt;span&gt;TerrainTile::Floor)
&lt;&#x2F;span&gt;&lt;span&gt;            .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;choose_multiple&lt;&#x2F;span&gt;&lt;span&gt;(rng, n)
&lt;&#x2F;span&gt;&lt;span&gt;        {
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; item &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= match&lt;&#x2F;span&gt;&lt;span&gt; rng.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;gen_range&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;..&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;100&lt;&#x2F;span&gt;&lt;span&gt;) {
&lt;&#x2F;span&gt;&lt;span&gt;                &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;..=&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;29 &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span&gt;ItemType::FireballScroll,
&lt;&#x2F;span&gt;&lt;span&gt;                &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;30&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;..=&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;49 &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span&gt;ItemType::ConfusionScroll,
&lt;&#x2F;span&gt;&lt;span&gt;                &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;_ =&amp;gt; &lt;&#x2F;span&gt;&lt;span&gt;ItemType::HealthPotion,
&lt;&#x2F;span&gt;&lt;span&gt;            };
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;*&lt;&#x2F;span&gt;&lt;span&gt;grid.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;get_checked_mut&lt;&#x2F;span&gt;&lt;span&gt;(coord) &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Some&lt;&#x2F;span&gt;&lt;span&gt;(TerrainTile::Item(item));
&lt;&#x2F;span&gt;&lt;span&gt;        }
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;When a character becomes confused, they will move randomly for 5 turns. To keep track of the number of turns
until a confused character recovers, add a &lt;code&gt;confusion_countdown&lt;&#x2F;code&gt; component. Entities which have this component
will be considered to be confused, and it will also track the time until recovery.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; world.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;entity_table::declare_entity_module&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;! &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;    components {
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;        confusion_countdown: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;u32&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;..
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Set the &lt;code&gt;UsageType&lt;&#x2F;code&gt; for confusion scrolls, spawn a projectile when a confusion scroll is used, and set what happens
when a confusion spell hits an NPC.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; world.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;impl &lt;&#x2F;span&gt;&lt;span&gt;World {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;pub fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;maybe_use_item&lt;&#x2F;span&gt;&lt;span&gt;(
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut &lt;&#x2F;span&gt;&lt;span&gt;self,
&lt;&#x2F;span&gt;&lt;span&gt;        character: Entity,
&lt;&#x2F;span&gt;&lt;span&gt;        inventory_index: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;usize&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;        message_log: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Vec&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;LogMessage&amp;gt;,
&lt;&#x2F;span&gt;&lt;span&gt;    ) -&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Result&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;ItemUsage, ()&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; usage &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= match&lt;&#x2F;span&gt;&lt;span&gt; item_type {
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;            ItemType::FireballScroll &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;| &lt;&#x2F;span&gt;&lt;span&gt;ItemType::ConfusionScroll &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span&gt;ItemUsage::Aim,
&lt;&#x2F;span&gt;&lt;span&gt;        };
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;pub fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;maybe_use_item_aim&lt;&#x2F;span&gt;&lt;span&gt;(
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut &lt;&#x2F;span&gt;&lt;span&gt;self,
&lt;&#x2F;span&gt;&lt;span&gt;        character: Entity,
&lt;&#x2F;span&gt;&lt;span&gt;        inventory_index: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;usize&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;        target: Coord,
&lt;&#x2F;span&gt;&lt;span&gt;        message_log: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Vec&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;LogMessage&amp;gt;,
&lt;&#x2F;span&gt;&lt;span&gt;    ) -&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Result&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;(), ()&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;match&lt;&#x2F;span&gt;&lt;span&gt; item_type {
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;            ItemType::ConfusionScroll &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;                message_log.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;push&lt;&#x2F;span&gt;&lt;span&gt;(LogMessage::PlayerLaunchesProjectile(
&lt;&#x2F;span&gt;&lt;span&gt;                    ProjectileType::Confusion,
&lt;&#x2F;span&gt;&lt;span&gt;                ));
&lt;&#x2F;span&gt;&lt;span&gt;                self.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;spawn_projectile&lt;&#x2F;span&gt;&lt;span&gt;(character_coord, target, ProjectileType::Confusion);
&lt;&#x2F;span&gt;&lt;span&gt;            }
&lt;&#x2F;span&gt;&lt;span&gt;        }
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Ok&lt;&#x2F;span&gt;&lt;span&gt;(())
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;pub fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;move_projectiles&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut &lt;&#x2F;span&gt;&lt;span&gt;self, message_log: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Vec&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;LogMessage&amp;gt;) {
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;let mut&lt;&#x2F;span&gt;&lt;span&gt; confusion_hit &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Vec&lt;&#x2F;span&gt;&lt;span&gt;::new();
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;for &lt;&#x2F;span&gt;&lt;span&gt;(entity, trajectory) &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;in &lt;&#x2F;span&gt;&lt;span&gt;self.components.trajectory.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;iter_mut&lt;&#x2F;span&gt;&lt;span&gt;() {
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;if let &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Some&lt;&#x2F;span&gt;&lt;span&gt;(direction) &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; trajectory.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;next&lt;&#x2F;span&gt;&lt;span&gt;() {
&lt;&#x2F;span&gt;&lt;span&gt;                &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;if&lt;&#x2F;span&gt;&lt;span&gt; dest_layers.feature.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;is_some&lt;&#x2F;span&gt;&lt;span&gt;() {
&lt;&#x2F;span&gt;&lt;span&gt;                    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;                } &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;else if let &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Some&lt;&#x2F;span&gt;&lt;span&gt;(character) &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; dest_layers.character {
&lt;&#x2F;span&gt;&lt;span&gt;                    entities_to_remove.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;push&lt;&#x2F;span&gt;&lt;span&gt;(entity);
&lt;&#x2F;span&gt;&lt;span&gt;                    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;if let &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Some&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;projectile_type) &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span&gt;self.components.projectile.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;get&lt;&#x2F;span&gt;&lt;span&gt;(entity) {
&lt;&#x2F;span&gt;&lt;span&gt;                        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;match&lt;&#x2F;span&gt;&lt;span&gt; projectile_type {
&lt;&#x2F;span&gt;&lt;span&gt;                            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;                            ProjectileType::Confusion &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;                                confusion_hit.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;push&lt;&#x2F;span&gt;&lt;span&gt;(character);
&lt;&#x2F;span&gt;&lt;span&gt;                            }
&lt;&#x2F;span&gt;&lt;span&gt;                        }
&lt;&#x2F;span&gt;&lt;span&gt;                    }
&lt;&#x2F;span&gt;&lt;span&gt;                }
&lt;&#x2F;span&gt;&lt;span&gt;                &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;            }
&lt;&#x2F;span&gt;&lt;span&gt;        }
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;for&lt;&#x2F;span&gt;&lt;span&gt; entity &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;in&lt;&#x2F;span&gt;&lt;span&gt; confusion_hit {
&lt;&#x2F;span&gt;&lt;span&gt;            self.components.confusion_countdown.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;insert&lt;&#x2F;span&gt;&lt;span&gt;(entity, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;5&lt;&#x2F;span&gt;&lt;span&gt;);
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;if let &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Some&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;npc_type) &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span&gt;self.components.npc_type.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;get&lt;&#x2F;span&gt;&lt;span&gt;(entity) {
&lt;&#x2F;span&gt;&lt;span&gt;                message_log.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;push&lt;&#x2F;span&gt;&lt;span&gt;(LogMessage::NpcBecomesConfused(npc_type));
&lt;&#x2F;span&gt;&lt;span&gt;            }
&lt;&#x2F;span&gt;&lt;span&gt;        }
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Add and handle log messages for becoming confused and recovering.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; game.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;pub enum &lt;&#x2F;span&gt;&lt;span&gt;LogMessage {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;    NpcBecomesConfused(NpcType),
&lt;&#x2F;span&gt;&lt;span&gt;    NpcIsNoLongerConfused(NpcType),
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; ui.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;impl&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;#39;a&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt; View&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&amp;#39;a &lt;&#x2F;span&gt;&lt;span&gt;[LogMessage]&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;for &lt;&#x2F;span&gt;&lt;span&gt;MessagesView {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;view&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;F: Frame, C: ColModify&amp;gt;(
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut &lt;&#x2F;span&gt;&lt;span&gt;self,
&lt;&#x2F;span&gt;&lt;span&gt;        messages: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&amp;#39;a&lt;&#x2F;span&gt;&lt;span&gt; [LogMessage],
&lt;&#x2F;span&gt;&lt;span&gt;        context: ViewContext&amp;lt;C&amp;gt;,
&lt;&#x2F;span&gt;&lt;span&gt;        frame: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut&lt;&#x2F;span&gt;&lt;span&gt; F,
&lt;&#x2F;span&gt;&lt;span&gt;    ) {
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;format_message&lt;&#x2F;span&gt;&lt;span&gt;(buf: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut&lt;&#x2F;span&gt;&lt;span&gt; [RichTextPartOwned], message: LogMessage) {
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;match&lt;&#x2F;span&gt;&lt;span&gt; message {
&lt;&#x2F;span&gt;&lt;span&gt;                &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;                NpcBecomesConfused(npc_type) &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;                    write!(&amp;amp;mut buf[0].text, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;The &amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;).&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;unwrap&lt;&#x2F;span&gt;&lt;span&gt;();
&lt;&#x2F;span&gt;&lt;span&gt;                    write!(&amp;amp;mut buf[1].text, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;{}&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;, npc_type.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;name&lt;&#x2F;span&gt;&lt;span&gt;()).&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;unwrap&lt;&#x2F;span&gt;&lt;span&gt;();
&lt;&#x2F;span&gt;&lt;span&gt;                    buf[&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;].style.foreground &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Some&lt;&#x2F;span&gt;&lt;span&gt;(colours::npc_colour(npc_type));
&lt;&#x2F;span&gt;&lt;span&gt;                    write!(&amp;amp;mut buf[2].text, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot; is confused.&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;).&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;unwrap&lt;&#x2F;span&gt;&lt;span&gt;();
&lt;&#x2F;span&gt;&lt;span&gt;                }
&lt;&#x2F;span&gt;&lt;span&gt;                NpcIsNoLongerConfused(npc_type) &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;                    write!(&amp;amp;mut buf[0].text, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;The &amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;).&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;unwrap&lt;&#x2F;span&gt;&lt;span&gt;();
&lt;&#x2F;span&gt;&lt;span&gt;                    write!(&amp;amp;mut buf[1].text, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;{}&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;, npc_type.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;name&lt;&#x2F;span&gt;&lt;span&gt;()).&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;unwrap&lt;&#x2F;span&gt;&lt;span&gt;();
&lt;&#x2F;span&gt;&lt;span&gt;                    buf[&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;].style.foreground &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Some&lt;&#x2F;span&gt;&lt;span&gt;(colours::npc_colour(npc_type));
&lt;&#x2F;span&gt;&lt;span&gt;                    write!(&amp;amp;mut buf[2].text, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;&amp;#39;s confusion passes.&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;).&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;unwrap&lt;&#x2F;span&gt;&lt;span&gt;();
&lt;&#x2F;span&gt;&lt;span&gt;                }
&lt;&#x2F;span&gt;&lt;span&gt;            }
&lt;&#x2F;span&gt;&lt;span&gt;        }
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Now let’s update movement logic such that confused characters move in random directions.
To use the &lt;code&gt;Rng&lt;&#x2F;code&gt; trait to select a random direction, we need to enable the optional &lt;code&gt;rand&lt;&#x2F;code&gt;
feature of the &lt;code&gt;direction&lt;&#x2F;code&gt; crate.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;toml&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-toml &quot;&gt;&lt;code class=&quot;language-toml&quot; data-lang=&quot;toml&quot;&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;# Cargo.toml
&lt;&#x2F;span&gt;&lt;span&gt;[dependencies]
&lt;&#x2F;span&gt;&lt;span style=&quot;background-color:#f5f5f5;font-weight:bold;color:#b52a1d;&quot;&gt;...&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#63a35c;&quot;&gt;direction &lt;&#x2F;span&gt;&lt;span&gt;= { &lt;&#x2F;span&gt;&lt;span style=&quot;color:#63a35c;&quot;&gt;version &lt;&#x2F;span&gt;&lt;span&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;0.18&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#63a35c;&quot;&gt;features &lt;&#x2F;span&gt;&lt;span&gt;= [&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;rand&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;] }
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;In &lt;code&gt;game.rs&lt;&#x2F;code&gt;, start passing a rng to &lt;code&gt;World::maybe_move_character&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; game.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;impl &lt;&#x2F;span&gt;&lt;span&gt;GameState {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;pub fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;maybe_move_player&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut &lt;&#x2F;span&gt;&lt;span&gt;self, direction: CardinalDirection) {
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;        self.world.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;maybe_move_character&lt;&#x2F;span&gt;&lt;span&gt;(
&lt;&#x2F;span&gt;&lt;span&gt;            self.player_entity,
&lt;&#x2F;span&gt;&lt;span&gt;            direction,
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut &lt;&#x2F;span&gt;&lt;span&gt;self.message_log,
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut &lt;&#x2F;span&gt;&lt;span&gt;self.rng,
&lt;&#x2F;span&gt;&lt;span&gt;        );
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;ai_turn&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut &lt;&#x2F;span&gt;&lt;span&gt;self) {
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;for &lt;&#x2F;span&gt;&lt;span&gt;(entity, agent) &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;in &lt;&#x2F;span&gt;&lt;span&gt;self.ai_state.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;iter_mut&lt;&#x2F;span&gt;&lt;span&gt;() {
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;match&lt;&#x2F;span&gt;&lt;span&gt; npc_action {
&lt;&#x2F;span&gt;&lt;span&gt;                &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;                NpcAction::Move(direction) &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span&gt;self.world.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;maybe_move_character&lt;&#x2F;span&gt;&lt;span&gt;(
&lt;&#x2F;span&gt;&lt;span&gt;                    entity,
&lt;&#x2F;span&gt;&lt;span&gt;                    direction,
&lt;&#x2F;span&gt;&lt;span&gt;                    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut &lt;&#x2F;span&gt;&lt;span&gt;self.message_log,
&lt;&#x2F;span&gt;&lt;span&gt;                    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut &lt;&#x2F;span&gt;&lt;span&gt;self.rng,
&lt;&#x2F;span&gt;&lt;span&gt;                ),
&lt;&#x2F;span&gt;&lt;span&gt;            }
&lt;&#x2F;span&gt;&lt;span&gt;        }
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;And update &lt;code&gt;World::maybe_move_character&lt;&#x2F;code&gt; to take an rng as an argument and use it
to move characters randomly when they are confused, also decreasing, and eventually removing,
their &lt;code&gt;confusion_countdown&lt;&#x2F;code&gt; component.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; world.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;impl &lt;&#x2F;span&gt;&lt;span&gt;World {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;pub fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;maybe_move_character&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;R: Rng&amp;gt;(
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut &lt;&#x2F;span&gt;&lt;span&gt;self,
&lt;&#x2F;span&gt;&lt;span&gt;        character_entity: Entity,
&lt;&#x2F;span&gt;&lt;span&gt;        direction: CardinalDirection,
&lt;&#x2F;span&gt;&lt;span&gt;        message_log: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Vec&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;LogMessage&amp;gt;,
&lt;&#x2F;span&gt;&lt;span&gt;        rng: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut&lt;&#x2F;span&gt;&lt;span&gt; R,
&lt;&#x2F;span&gt;&lt;span&gt;    ) {
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; character_coord &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span&gt;self
&lt;&#x2F;span&gt;&lt;span&gt;            .spatial_table
&lt;&#x2F;span&gt;&lt;span&gt;            .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;coord_of&lt;&#x2F;span&gt;&lt;span&gt;(character_entity)
&lt;&#x2F;span&gt;&lt;span&gt;            .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;expect&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;character has no coord&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;);
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; direction &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= if let &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Some&lt;&#x2F;span&gt;&lt;span&gt;(confusion_countdown) &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span&gt;self
&lt;&#x2F;span&gt;&lt;span&gt;            .components
&lt;&#x2F;span&gt;&lt;span&gt;            .confusion_countdown
&lt;&#x2F;span&gt;&lt;span&gt;            .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;get_mut&lt;&#x2F;span&gt;&lt;span&gt;(character_entity)
&lt;&#x2F;span&gt;&lt;span&gt;        {
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;if *&lt;&#x2F;span&gt;&lt;span&gt;confusion_countdown &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;== &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;0 &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;                self.components.confusion_countdown.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;remove&lt;&#x2F;span&gt;&lt;span&gt;(character_entity);
&lt;&#x2F;span&gt;&lt;span&gt;                &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;if let &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Some&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;npc_type) &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span&gt;self.components.npc_type.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;get&lt;&#x2F;span&gt;&lt;span&gt;(character_entity) {
&lt;&#x2F;span&gt;&lt;span&gt;                    message_log.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;push&lt;&#x2F;span&gt;&lt;span&gt;(LogMessage::NpcIsNoLongerConfused(npc_type));
&lt;&#x2F;span&gt;&lt;span&gt;                }
&lt;&#x2F;span&gt;&lt;span&gt;            } &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;else &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;                &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;*&lt;&#x2F;span&gt;&lt;span&gt;confusion_countdown &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;-= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;;
&lt;&#x2F;span&gt;&lt;span&gt;            }
&lt;&#x2F;span&gt;&lt;span&gt;            rng.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;gen&lt;&#x2F;span&gt;&lt;span&gt;()
&lt;&#x2F;span&gt;&lt;span&gt;        } &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;else &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;            direction
&lt;&#x2F;span&gt;&lt;span&gt;        };
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; new_character_coord &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; character_coord &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;+&lt;&#x2F;span&gt;&lt;span&gt; direction.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;coord&lt;&#x2F;span&gt;&lt;span&gt;();
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;It’s now possible to launch confusion spells in the same way as you launch fireballs.
NPCs hit with confusion spells move randomly for their next 5 turns.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;roguelike-tutorial-2020-part-9&#x2F;confusion.png&quot; alt=&quot;confusion.png&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Reference implementation branch: &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;gridbugs&#x2F;chargrid-roguelike-tutorial-2020&#x2F;tree&#x2F;part-9.3&quot;&gt;part-9.3&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;a href=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;roguelike-tutorial-2020-part-10&#x2F;&quot;&gt;Click here for the next part!&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Part 8 - Items and Inventory</title>
          <pubDate>Wed, 29 Jul 2020 18:00:00 +1000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/roguelike-tutorial-2020-part-8/</link>
          <guid>https://www.gridbugs.org/roguelike-tutorial-2020-part-8/</guid>
          <description xml:base="https://www.gridbugs.org/roguelike-tutorial-2020-part-8/">&lt;p&gt;In this part we’ll introduce items, and add an inventory menu.&lt;&#x2F;p&gt;
&lt;p&gt;By the end of this part you’ll be able to pick up, use, and drop items.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;roguelike-tutorial-2020-part-8&#x2F;item-menu.png&quot; alt=&quot;item-menu.png&quot; &#x2F;&gt;&lt;&#x2F;p&gt;</description>
      </item>
      <item>
          <title>Part 6 - AI and Combat</title>
          <pubDate>Sun, 12 Jul 2020 17:00:00 +1000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/roguelike-tutorial-2020-part-6/</link>
          <guid>https://www.gridbugs.org/roguelike-tutorial-2020-part-6/</guid>
          <description xml:base="https://www.gridbugs.org/roguelike-tutorial-2020-part-6/">&lt;p&gt;In this part we’ll imbue NPCs with artificial intelligence, and make it possible
for them to deal and receive damage.&lt;&#x2F;p&gt;
&lt;p&gt;By the end of this part, the game will look like this:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;roguelike-tutorial-2020-part-6&#x2F;screenshot-end.png&quot; alt=&quot;screenshot-end.png&quot; &#x2F;&gt;&lt;&#x2F;p&gt;</description>
      </item>
      <item>
          <title>Part 7 - User Interface</title>
          <pubDate>Sun, 12 Jul 2020 17:00:00 +1000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/roguelike-tutorial-2020-part-7/</link>
          <guid>https://www.gridbugs.org/roguelike-tutorial-2020-part-7/</guid>
          <description xml:base="https://www.gridbugs.org/roguelike-tutorial-2020-part-7/">&lt;p&gt;In this part we’ll add a heads-up display consisting of a health bar and message log.&lt;&#x2F;p&gt;
&lt;p&gt;By the end of this part, the game will look like this:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;roguelike-tutorial-2020-part-7&#x2F;screenshot-end.png&quot; alt=&quot;screenshot-end.png&quot; &#x2F;&gt;&lt;&#x2F;p&gt;</description>
      </item>
      <item>
          <title>Part 4 - Field of View</title>
          <pubDate>Thu, 02 Jul 2020 22:00:00 +1000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/roguelike-tutorial-2020-part-4/</link>
          <guid>https://www.gridbugs.org/roguelike-tutorial-2020-part-4/</guid>
          <description xml:base="https://www.gridbugs.org/roguelike-tutorial-2020-part-4/">&lt;p&gt;In this part we’ll implement visible area detection, so players can only see what their
character can see, and what they remember seeing.&lt;&#x2F;p&gt;
&lt;p&gt;By the end of this part, the game will look like this:
&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;roguelike-tutorial-2020-part-4&#x2F;screenshot-end.png&quot; alt=&quot;screenshot-end.png&quot; &#x2F;&gt;&lt;&#x2F;p&gt;</description>
      </item>
      <item>
          <title>Part 5 - Placing NPCs</title>
          <pubDate>Thu, 02 Jul 2020 22:00:00 +1000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/roguelike-tutorial-2020-part-5/</link>
          <guid>https://www.gridbugs.org/roguelike-tutorial-2020-part-5/</guid>
          <description xml:base="https://www.gridbugs.org/roguelike-tutorial-2020-part-5/">&lt;p&gt;In this part we’ll populate the dungeon with enemies.
There won’t be any AI or combat. That will come later.&lt;&#x2F;p&gt;
&lt;p&gt;By the end of this part, the game will look like this:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;roguelike-tutorial-2020-part-5&#x2F;screenshot-end.png&quot; alt=&quot;screenshot-end.png&quot; &#x2F;&gt;&lt;&#x2F;p&gt;</description>
      </item>
      <item>
          <title>Part 2 - Entities, Rendering, Map</title>
          <pubDate>Fri, 19 Jun 2020 20:00:00 +1000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/roguelike-tutorial-2020-part-2/</link>
          <guid>https://www.gridbugs.org/roguelike-tutorial-2020-part-2/</guid>
          <description xml:base="https://www.gridbugs.org/roguelike-tutorial-2020-part-2/">&lt;p&gt;In the &lt;a href=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;roguelike-tutorial-2020-part-1&#x2F;&quot;&gt;previous part&lt;&#x2F;a&gt; we got a single ‘@’ sign moving
around the screen. The player character was represented by a coordinate stored directly in
the game state. In this part, we’ll define a generic “entity” type, of which the player character
is merely one instance. The rendering logic will be generalized to draw arbitrary game entities.
Finally, we’ll use the generic entity type to define map components - namely walls and
floor tiles.&lt;&#x2F;p&gt;
&lt;p&gt;By the end of this part, the game will look like this:
&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;roguelike-tutorial-2020-part-2&#x2F;screenshot-end.png&quot; alt=&quot;screenshot-end.png&quot; &#x2F;&gt;&lt;&#x2F;p&gt;</description>
      </item>
      <item>
          <title>Part 3 - Generating a Dungeon</title>
          <pubDate>Fri, 19 Jun 2020 20:00:00 +1000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/roguelike-tutorial-2020-part-3/</link>
          <guid>https://www.gridbugs.org/roguelike-tutorial-2020-part-3/</guid>
          <description xml:base="https://www.gridbugs.org/roguelike-tutorial-2020-part-3/">&lt;p&gt;The definition of “roguelike” is &lt;a href=&quot;http:&#x2F;&#x2F;www.gamesofgrey.com&#x2F;blog&#x2F;?p=403&quot;&gt;hotly debated&lt;&#x2F;a&gt;
but one aspect we can all agree on is that levels must be procedurally generated.
That is, rather than fixed, hand-crafted levels, players will explore levels generated
according to an algorithm; each playthrough will be unique, and it’s &lt;em&gt;highly&lt;&#x2F;em&gt; unlikely that
any other player will ever see the same levels as you.&lt;&#x2F;p&gt;
&lt;p&gt;In this part we’ll implement an algorithm for procedurally generating a dungeon!&lt;&#x2F;p&gt;
&lt;p&gt;By the end of this part, the game will look like this:
&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;roguelike-tutorial-2020-part-3&#x2F;screenshot-end.png&quot; alt=&quot;screenshot-end.png&quot; &#x2F;&gt;&lt;&#x2F;p&gt;</description>
      </item>
      <item>
          <title>Part 0 - Setting Up</title>
          <pubDate>Fri, 12 Jun 2020 20:00:00 +1000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/roguelike-tutorial-2020-part-0/</link>
          <guid>https://www.gridbugs.org/roguelike-tutorial-2020-part-0/</guid>
          <description xml:base="https://www.gridbugs.org/roguelike-tutorial-2020-part-0/">&lt;p&gt;This is the first of a series of posts where I follow the
&lt;a href=&quot;http:&#x2F;&#x2F;rogueliketutorials.com&#x2F;&quot;&gt;python tcod roguelike tutorial&lt;&#x2F;a&gt;
but instead of programming in python using the
&lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;libtcod&#x2F;libtcod&quot;&gt;tcod&lt;&#x2F;a&gt; library, I’ll be programming
in rust using the &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;gridbugs&#x2F;chargrid&quot;&gt;chargrid&lt;&#x2F;a&gt;
library, which I’ve been developing for about 3 years.&lt;&#x2F;p&gt;
&lt;p&gt;This is part of an event where the &lt;a href=&quot;https:&#x2F;&#x2F;old.reddit.com&#x2F;r&#x2F;roguelikedev&#x2F;wiki&#x2F;python_tutorial_series&quot;&gt;roguelikedev subreddit does the complete
roguelike tutorial&lt;&#x2F;a&gt;
over the course of several weeks.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;roguelike-tutorial-2020-part-0&#x2F;logo.png&quot; alt=&quot;logo.png&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;h2 id=&quot;installation&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#installation&quot; aria-label=&quot;Anchor link for: installation&quot;&gt;Installation&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;To follow this tutorial you will need a rust compiler. Follow the instructions &lt;a href=&quot;https:&#x2F;&#x2F;www.rust-lang.org&#x2F;tools&#x2F;install&quot;&gt;here&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;new-rust-project&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#new-rust-project&quot; aria-label=&quot;Anchor link for: new-rust-project&quot;&gt;New Rust Project&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;After rust is installed you’ll have a command named &lt;code&gt;cargo&lt;&#x2F;code&gt;. Use it to create a new rust project:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;$ cargo new --bin chargrid-roguelike-tutorial-2020
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This command creates a directory structure:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;├── Cargo.toml   # manifest - we&amp;#39;ll mainly update it to add dependencies
&lt;&#x2F;span&gt;&lt;span&gt;└── src          # all the project&amp;#39;s source code will live under this directory
&lt;&#x2F;span&gt;&lt;span&gt;    └── main.rs  # entry point for the program
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;From within the “chargrid-roguelike-tutorial-2020” directory, run:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;bash&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-bash &quot;&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span&gt;cargo run
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This will compile and run the program. The output should be something like:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;   Compiling chargrid-roguelike-tutorial-2020 v0.1.0
&lt;&#x2F;span&gt;&lt;span&gt;    Finished dev [unoptimized + debuginfo] target(s) in 0.16s
&lt;&#x2F;span&gt;&lt;span&gt;     Running `target&#x2F;debug&#x2F;chargrid-roguelike-tutorial-2020`
&lt;&#x2F;span&gt;&lt;span&gt;Hello, world!
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h2 id=&quot;get-fonts&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#get-fonts&quot; aria-label=&quot;Anchor link for: get-fonts&quot;&gt;Get Fonts&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;We’ll be making a traditional roguelike, and that means text-only graphics.
You’ll need a font. Chargrid requires a pair of fonts - one for regular text and a second for bold.
Download ttf files for a pair of fonts. They must be monospace (all characters are the same width).&lt;&#x2F;p&gt;
&lt;p&gt;Two suitable fonts can be downloaded here:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;roguelike-tutorial-2020-part-0&#x2F;PxPlus_IBM_CGAthin.ttf&quot;&gt;IBM PxPlus Regular TTF&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;roguelike-tutorial-2020-part-0&#x2F;PxPlus_IBM_CGA.ttf&quot;&gt;IBM PxPlus Bold TTF&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Once you have your fonts, make a “fonts” directory inside your “src” directory and place the
fonts there.&lt;&#x2F;p&gt;
&lt;p&gt;Your source tree should now look like this:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;├── Cargo.toml
&lt;&#x2F;span&gt;&lt;span&gt;└── src
&lt;&#x2F;span&gt;&lt;span&gt;    ├── fonts
&lt;&#x2F;span&gt;&lt;span&gt;    │   ├── PxPlus_IBM_CGAthin.ttf
&lt;&#x2F;span&gt;&lt;span&gt;    │   └── PxPlus_IBM_CGA.ttf
&lt;&#x2F;span&gt;&lt;span&gt;    └── main.rs
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h2 id=&quot;get-dependencies&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#get-dependencies&quot; aria-label=&quot;Anchor link for: get-dependencies&quot;&gt;Get Dependencies&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;h3 id=&quot;linker&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#linker&quot; aria-label=&quot;Anchor link for: linker&quot;&gt;Linker&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;You’ll need a linker in order to build the code. Some Linux distributions provide a meta package of
common build tools which will contain a linker.&lt;&#x2F;p&gt;
&lt;h4 id=&quot;ubuntu&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#ubuntu&quot; aria-label=&quot;Anchor link for: ubuntu&quot;&gt;Ubuntu&lt;&#x2F;a&gt;&lt;&#x2F;h4&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;# apt install build-essential
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h4 id=&quot;arch-linux&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#arch-linux&quot; aria-label=&quot;Anchor link for: arch-linux&quot;&gt;Arch Linux&lt;&#x2F;a&gt;&lt;&#x2F;h4&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;# pacman -S base-devel
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Worst case, installing &lt;code&gt;gcc&lt;&#x2F;code&gt; or &lt;code&gt;clang&lt;&#x2F;code&gt; will ensure you have a linker installed as well.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;libx11-linux-only&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#libx11-linux-only&quot; aria-label=&quot;Anchor link for: libx11-linux-only&quot;&gt;libx11 (Linux only)&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;The code in this tutorial has a compile-time dependency on libx11.&lt;&#x2F;p&gt;
&lt;h4 id=&quot;ubuntu-1&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#ubuntu-1&quot; aria-label=&quot;Anchor link for: ubuntu-1&quot;&gt;Ubuntu&lt;&#x2F;a&gt;&lt;&#x2F;h4&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;# apt install libx11-dev
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h4 id=&quot;arch-linux-1&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#arch-linux-1&quot; aria-label=&quot;Anchor link for: arch-linux-1&quot;&gt;Arch Linux&lt;&#x2F;a&gt;&lt;&#x2F;h4&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;# pacman -S libx11
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h2 id=&quot;reference-implementation&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#reference-implementation&quot; aria-label=&quot;Anchor link for: reference-implementation&quot;&gt;Reference Implementation&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;If you get stuck, or something in these tutorials doesn’t make sense, take a look at the git repo at
&lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;gridbugs&#x2F;chargrid-roguelike-tutorial-2020&quot;&gt;https:&#x2F;&#x2F;github.com&#x2F;gridbugs&#x2F;chargrid-roguelike-tutorial-2020&lt;&#x2F;a&gt;.
For each part and subsection of this tutorial, there is a corresponding branch showing the state of the repo
at that stage of the tutorial. This makes it easy to show the changes introduced in each part of the tutorial.&lt;&#x2F;p&gt;
&lt;p&gt;For example to show the change to &lt;code&gt;src&#x2F;main.rs&lt;&#x2F;code&gt; between part 0.0 and 1.0, clone the repo and run:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;git diff part-0.0 part-1.0 src&#x2F;main.rs
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;pre data-lang=&quot;diff&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-diff &quot;&gt;&lt;code class=&quot;language-diff&quot; data-lang=&quot;diff&quot;&gt;&lt;span&gt;diff --git a&#x2F;src&#x2F;main.rs b&#x2F;src&#x2F;main.rs
&lt;&#x2F;span&gt;&lt;span&gt;index e7a11a9..b19bcfb 100644
&lt;&#x2F;span&gt;&lt;span style=&quot;background-color:#ffecec;font-weight:bold;font-style:italic;color:#bd2c00;&quot;&gt;---&lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt; a&#x2F;src&#x2F;main.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;background-color:#eaffea;font-weight:bold;font-style:italic;color:#55a532;&quot;&gt;+++&lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt; b&#x2F;src&#x2F;main.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;font-style:italic;color:#969896;&quot;&gt;@@ -1,3 +1,60 @@
&lt;&#x2F;span&gt;&lt;span&gt; fn main() {
&lt;&#x2F;span&gt;&lt;span style=&quot;background-color:#ffecec;color:#323232;&quot;&gt;-    println!(&amp;quot;Hello, world!&amp;quot;);
&lt;&#x2F;span&gt;&lt;span style=&quot;background-color:#eaffea;font-weight:bold;color:#55a532;&quot;&gt;+&lt;&#x2F;span&gt;&lt;span style=&quot;background-color:#eaffea;color:#323232;&quot;&gt;    use chargrid_graphical::{Config, Context, Dimensions, FontBytes};
&lt;&#x2F;span&gt;&lt;span style=&quot;background-color:#eaffea;font-weight:bold;color:#55a532;&quot;&gt;+&lt;&#x2F;span&gt;&lt;span style=&quot;background-color:#eaffea;color:#323232;&quot;&gt;    const CELL_SIZE_PX: f64 = 24.;
&lt;&#x2F;span&gt;&lt;span style=&quot;background-color:#eaffea;font-weight:bold;color:#55a532;&quot;&gt;+&lt;&#x2F;span&gt;&lt;span style=&quot;background-color:#eaffea;color:#323232;&quot;&gt;    let context = Context::new(Config {
&lt;&#x2F;span&gt;&lt;span&gt;...
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Reference implementation branch: &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;gridbugs&#x2F;chargrid-roguelike-tutorial-2020&#x2F;tree&#x2F;part-0.0&quot;&gt;part-0.0&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;h2 id=&quot;code-snippets&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#code-snippets&quot; aria-label=&quot;Anchor link for: code-snippets&quot;&gt;Code Snippets&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;Throughout this tutorial there will be many code snippets!
In fact all the code that makes up the reference implementation will be present at some point in the tutorial.
As we progress through building this roguelike, there will be times when we update existing code.
Whenever code changes, the new version of the code will be shown, with (hopefully!) enough context for the reader
to understand the old code that it’s replacing and update their implementation accordingly.&lt;&#x2F;p&gt;
&lt;p&gt;If it’s not clear, consult the reference implementation to see exactly what changes between each section of each part.&lt;&#x2F;p&gt;
&lt;p&gt;There may be points in the middle of sections (between headings) where the code doesn’t compile, however at each heading
in each part, the code will be in a compiling state. Each heading corresponds to a particular &lt;code&gt;part-x.y&lt;&#x2F;code&gt; branch
in the reference implementation repository, and each such branch should always be in a compilable state.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;ready-to-begin&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#ready-to-begin&quot; aria-label=&quot;Anchor link for: ready-to-begin&quot;&gt;Ready to begin?&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;&lt;a href=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;roguelike-tutorial-2020-part-1&#x2F;&quot;&gt;Click here for part 1 of the tutorial!&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Part 1 - Drawing and Moving the Player</title>
          <pubDate>Fri, 12 Jun 2020 20:00:00 +1000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/roguelike-tutorial-2020-part-1/</link>
          <guid>https://www.gridbugs.org/roguelike-tutorial-2020-part-1/</guid>
          <description xml:base="https://www.gridbugs.org/roguelike-tutorial-2020-part-1/">&lt;p&gt;For getting set up for this tutorial, see &lt;a href=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;roguelike-tutorial-2020-part-0&#x2F;&quot;&gt;Part 0&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;This part will take you from printing “Hello, World!” to opening a window, drawing a ‘@’ symbol
(representing the player character) and moving the player around with the arrow keys.&lt;&#x2F;p&gt;
&lt;p&gt;By the end of this part, the game will look like this:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;roguelike-tutorial-2020-part-1&#x2F;screenshot.png&quot; alt=&quot;screenshot.png&quot; &#x2F;&gt;&lt;&#x2F;p&gt;</description>
      </item>
      <item>
          <title>Conway&#x27;s Game of Life on the NES in Rust</title>
          <pubDate>Thu, 21 May 2020 16:00:00 +1000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/conways-game-of-life-on-the-nes-in-rust/</link>
          <guid>https://www.gridbugs.org/conways-game-of-life-on-the-nes-in-rust/</guid>
          <description xml:base="https://www.gridbugs.org/conways-game-of-life-on-the-nes-in-rust/">&lt;style&gt;
.nes-3x3 img {
    image-rendering: crisp-edges;
    image-rendering: pixelated;
    width: 384px;
    height: 48px;
}
.pattern-table img {
    image-rendering: crisp-edges;
    image-rendering: pixelated;
    width: 256px;
    height: 256px;
}
.pattern img {
    image-rendering: crisp-edges;
    image-rendering: pixelated;
    width: 16px;
    height: 16px;
}
.nes-screenshot img {
    width: 512px;
    height: 480px;
    image-rendering: crisp-edges;
    image-rendering: pixelated;
}
&lt;&#x2F;style&gt;
&lt;p&gt;This post is about a Rust program…&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;$ cargo install conway-nes
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;…that prints out a NES binary…&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;$ conway-nes &amp;gt; life.nes
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;…that runs Conway’s Game of Life!&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;$ fceux life.nes    # fceux is a NES emulator
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;div class=&quot;nes-screenshot&quot;&gt;
&lt;img src=&quot;demo.webp&quot;&gt;
&lt;&#x2F;div&gt;</description>
      </item>
      <item>
          <title>slime99</title>
          <pubDate>Sun, 08 Mar 2020 06:00:00 +1000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/slime99/</link>
          <guid>https://www.gridbugs.org/slime99/</guid>
          <description xml:base="https://www.gridbugs.org/slime99/">&lt;p&gt;A traditional roguelike where the outcomes of attacking and defending are pre-determined and visible.
Gameplay revolves around fighting slimes, adding to your sequence of combat outcomes, and using
abilities to modify the order in which combat outcomes occur. It’s set in a neon sewer!&lt;&#x2F;p&gt;
&lt;p&gt;It’s my entry in the 2020 7 Day Roguelike game jam.&lt;&#x2F;p&gt;
&lt;p&gt;Play or download slime99 on &lt;a href=&quot;https:&#x2F;&#x2F;gridbugs.itch.io&#x2F;slime99&quot;&gt;its itch.io page&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;View the &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;gridbugs&#x2F;slime99&quot;&gt;source code on github&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;slime99&#x2F;screenshot.png&quot; alt=&quot;screenshot.png&quot; &#x2F;&gt;&lt;&#x2F;p&gt;</description>
      </item>
      <item>
          <title>7 Day Roguelike 2020: Day 7</title>
          <pubDate>Fri, 06 Mar 2020 22:00:00 +1000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/7drl2020-day7/</link>
          <guid>https://www.gridbugs.org/7drl2020-day7/</guid>
          <description xml:base="https://www.gridbugs.org/7drl2020-day7/">&lt;p&gt;It’s done! I spent today fleshing out the final boss fight, music, and lots and lots of playtesting and tweaking
until the game felt challenging but winnable (and fun!).&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;7drl2020-day7&#x2F;screenshot.png&quot; alt=&quot;screenshot.png&quot; &#x2F;&gt;&lt;&#x2F;p&gt;</description>
      </item>
      <item>
          <title>7 Day Roguelike 2020: Day 6</title>
          <pubDate>Thu, 05 Mar 2020 22:00:00 +1000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/7drl2020-day6/</link>
          <guid>https://www.gridbugs.org/7drl2020-day6/</guid>
          <description xml:base="https://www.gridbugs.org/7drl2020-day6/">&lt;p&gt;I spent today play-testing and tweaking mechanics to make them more fun and balanced.
Originally there were slimes which granted the player abilities when attacked.
These slimes would flee the player at half speed, and you would need to chase them
down while evading other slimes to get new attacks, defences and techs.
This proved tedious, so I cut them. Now all enemies have a chance to drop items when
killed. The green “goo” slimes drop better items, but they also spawn sludge when
killed, so you have to step in sludge and take damage to pick up the item.
There are also various items placed around the level, with better items being
placed in sludge.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;7drl2020-day6&#x2F;screenshot4.png&quot; alt=&quot;screenshot4.png&quot; &#x2F;&gt;&lt;&#x2F;p&gt;</description>
      </item>
      <item>
          <title>7 Day Roguelike 2020: Day 5</title>
          <pubDate>Wed, 04 Mar 2020 22:00:00 +1000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/7drl2020-day5/</link>
          <guid>https://www.gridbugs.org/7drl2020-day5/</guid>
          <description xml:base="https://www.gridbugs.org/7drl2020-day5/">&lt;p&gt;Today I focused on upgrades. There are certain enemies which grant attack, defense, and
tech items when damaged. Upon completion of a level, this dialog appears letting you
choose an ability (abilities let you manipulate the sequences of items).&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;7drl2020-day5&#x2F;screenshot.png&quot; alt=&quot;screenshot.png&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Tomorrow I need to play the game a ton and tweak mechanics until it is fun.
I have an idea for a final boss fight: A slime which divides when hit, into slimes with
random combinations of properties!&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>7 Day Roguelike 2020: Day 4</title>
          <pubDate>Tue, 03 Mar 2020 21:00:00 +1000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/7drl2020-day4/</link>
          <guid>https://www.gridbugs.org/7drl2020-day4/</guid>
          <description xml:base="https://www.gridbugs.org/7drl2020-day4/">&lt;p&gt;Lots of work on mechanics today. All the attack and defense abilities are implemented,
and most enemies do something interesting when attacked. Also, it’s possible (but not
advisable!) to walk on the green sludge found around the sewer. Most enemies are also
vulnerable to the sludge, which can be used to the player’s advantage.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;7drl2020-day4&#x2F;screenshot.png&quot; alt=&quot;screenshot.png&quot; &#x2F;&gt;&lt;&#x2F;p&gt;</description>
      </item>
      <item>
          <title>7 Day Roguelike 2020: Day 3</title>
          <pubDate>Mon, 02 Mar 2020 20:00:00 +1000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/7drl2020-day3/</link>
          <guid>https://www.gridbugs.org/7drl2020-day3/</guid>
          <description xml:base="https://www.gridbugs.org/7drl2020-day3/">&lt;p&gt;Today I connected the user-interface to gameplay. Each time the player deals
or receives damage, or uses a tech, the respective list decreases and the
relevant action is applied (though not all are implemented yet).
The most complicated part of this is the aim UI (the red line in the screenshot),
but this was largely adapting some existing code to work with a grid of 2x2
tiles.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;7drl2020-day3&#x2F;screenshot.png&quot; alt=&quot;screenshot.png&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Tomorrow I’ll implement all the combat outcomes, and give each type of enemy
a distinct effect which applies upon damage&#x2F;death.&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>7 Day Roguelike 2020: Day 2</title>
          <pubDate>Sun, 01 Mar 2020 20:00:00 +1000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/7drl2020-day2/</link>
          <guid>https://www.gridbugs.org/7drl2020-day2/</guid>
          <description xml:base="https://www.gridbugs.org/7drl2020-day2/">&lt;p&gt;Lots of visual changes today! I implemented a brand-new renderer. The most interesting thing about it
is every game cell is rendered as a 2x2 block of text cells. The obvious benefit of doing this is
the health of each enemy can be displayed on the enemy tiles, along with a letter indicating what
type of enemy they are, and an arrow indicating where they will move on their next turn.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;7drl2020-day2&#x2F;screenshot.png&quot; alt=&quot;screenshot.png&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Another neat side-effect of 2x2 rendering is the stairs can actually look like stairs (rendered using block characters)! I had to improvise a symbol for
the player character. I made an arrangement of box-drawing characters that kinda looks like a futuristic ‘@’ if you squint!&lt;&#x2F;p&gt;</description>
      </item>
      <item>
          <title>7 Day Roguelike 2020: Day 1</title>
          <pubDate>Sat, 29 Feb 2020 20:00:00 +1000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/7drl2020-day1/</link>
          <guid>https://www.gridbugs.org/7drl2020-day1/</guid>
          <description xml:base="https://www.gridbugs.org/7drl2020-day1/">&lt;p&gt;Today I focused on procedural generation. I have a small playable demo of a
procedurally-generated sewer. Walls are placed using
&lt;a href=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;wave-function-collapse&#x2F;&quot;&gt;wave function collapse&lt;&#x2F;a&gt;,
then sludge pools, bridges, doors, and the start and goal locations are chosen
based on hand-crafted rules. I then spent an hour or so integrating the level
generator into the &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;gridbugs&#x2F;rip&quot;&gt;RIP&lt;&#x2F;a&gt; engine, and messing
around with graphics and lighting.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;7drl2020-day1&#x2F;screenshot.png&quot; alt=&quot;screenshot.png&quot; &#x2F;&gt;&lt;&#x2F;p&gt;</description>
      </item>
      <item>
          <title>7 Day Roguelike 2020: Plan</title>
          <pubDate>Sat, 29 Feb 2020 08:00:00 +1000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/7drl2020-plan/</link>
          <guid>https://www.gridbugs.org/7drl2020-plan/</guid>
          <description xml:base="https://www.gridbugs.org/7drl2020-plan/">&lt;style&gt;
.slime99 {
    color: rgb(0,255,255);
    background-color: rgb(255,0,255);
}
&lt;&#x2F;style&gt;
&lt;h2 id=&quot;slime99&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#slime99&quot; aria-label=&quot;Anchor link for: slime99&quot;&gt;&lt;span class=&quot;slime99&quot;&gt;Slime99&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;In the not-too-distant future,
&lt;span class=&quot;slime99&quot;&gt;&lt;strong&gt;THE YEAR 1999&lt;&#x2F;strong&gt;&lt;&#x2F;span&gt;
fallout from &lt;span class=&quot;slime99&quot;&gt;&lt;strong&gt;THE WAR&lt;&#x2F;strong&gt;&lt;&#x2F;span&gt; has caused
&lt;span class=&quot;slime99&quot;&gt;&lt;strong&gt;RADIOACTIVE MUTANT SLIMES&lt;&#x2F;strong&gt;&lt;&#x2F;span&gt; to appear in the
sewers of &lt;span class=&quot;slime99&quot;&gt;&lt;strong&gt;THE CITY&lt;&#x2F;strong&gt;&lt;&#x2F;span&gt;. You are a
&lt;span class=&quot;slime99&quot;&gt;&lt;strong&gt;GENETICALLY-MODIFIED PRECOG SUPER-SOLDIER&lt;&#x2F;strong&gt;&lt;&#x2F;span&gt;, whose free-will was in-part traded for the power
to &lt;span class=&quot;slime99&quot;&gt;&lt;strong&gt;PREDICT THE OUTCOME OF COMBAT ENCOUNTERS&lt;&#x2F;strong&gt;&lt;&#x2F;span&gt;. Go into the sewers and
&lt;span class=&quot;slime99&quot;&gt;&lt;strong&gt;ELIMINATE THE SOURCE OF SLIME!&lt;&#x2F;strong&gt;&lt;&#x2F;span&gt;&lt;&#x2F;p&gt;</description>
      </item>
      <item>
          <title>Zelda Screen Transitions are Undefined Behaviour</title>
          <pubDate>Thu, 20 Jun 2019 21:30:00 +1000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/zelda-screen-transitions-are-undefined-behaviour/</link>
          <guid>https://www.gridbugs.org/zelda-screen-transitions-are-undefined-behaviour/</guid>
          <description xml:base="https://www.gridbugs.org/zelda-screen-transitions-are-undefined-behaviour/">&lt;style&gt;
.nes-screenshot img {
    width: 512px;
    height: 480px;
    image-rendering: crisp-edges;
    image-rendering: pixelated;
}
&lt;&#x2F;style&gt;
&lt;p&gt;The vertical scrolling effect in the original “The Legend of Zelda” relies on
manipulating the NES graphics hardware in a manner that was likely unintended by its
designers.&lt;&#x2F;p&gt;
&lt;div class=&quot;nes-screenshot&quot;&gt;
&lt;img src=&quot;title.png&quot;&gt;
&lt;&#x2F;div&gt;
&lt;p&gt;Since I don’t have access
to any official documentation for the NES Picture Processing Unit
(PPU - the graphics chip), my claim of “undefined behaviour” is somewhat speculative.
I’ve been relying on the
&lt;a href=&quot;https:&#x2F;&#x2F;wiki.nesdev.com&#x2F;w&#x2F;index.php&#x2F;PPU&quot;&gt;NesDev Wiki&lt;&#x2F;a&gt; for a specification of how
the graphics hardware behaves. The PPU is controlled by writing to memory-mapped
registers. Using these registers for their (seemingly!) intended purposes,
the following effect should not be possible:&lt;&#x2F;p&gt;</description>
      </item>
      <item>
          <title>NES Emulator Debugging</title>
          <pubDate>Wed, 08 May 2019 08:00:00 +1000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/nes-emulator-debugging/</link>
          <guid>https://www.gridbugs.org/nes-emulator-debugging/</guid>
          <description xml:base="https://www.gridbugs.org/nes-emulator-debugging/">&lt;style&gt;
.nes-emulator-debugging-screenshot img {
    width: 512px;
    height: 480px;
    image-rendering: crisp-edges;
    image-rendering: pixelated;
}
.nes-emulator-debugging-screenshot video {
    width: 512px;
    height: 480px;
}


.nes-tile img {
    width: 64px;
    height: 64px;
    image-rendering: crisp-edges;
    image-rendering: pixelated;
}

.mario-render img {
    width: 328px;
    height: auto;
    image-rendering: crisp-edges;
    image-rendering: pixelated;
}
&lt;&#x2F;style&gt;
&lt;p&gt;Making an emulator for a 1980s game console is an exercise in reading and comprehension.
The work is mostly translating documentation into code.
It’s oddly satisfying, building a model of an ancient machine,
instruction by instruction, device by device, especially once it can start running real programs.
You end up with an appreciation for the capabilities (or lack thereof) of hardware at the time,
and out of necessity, end up intimately familiar with the inner workings of a piece of computing history.&lt;&#x2F;p&gt;
&lt;p&gt;This post is not about making an emulator.&lt;&#x2F;p&gt;
&lt;p&gt;It is about the nightmarish, overwhelmingly complex, and at times seemingly hopeless
task of hunting down the parts of your emulator that don’t behave exactly
like the real hardware.&lt;&#x2F;p&gt;
&lt;div class=&quot;nes-emulator-debugging-screenshot&quot;&gt;
&lt;img src=&quot;example.png&quot;&gt;
&lt;&#x2F;div&gt;</description>
      </item>
      <item>
          <title>Get Well Soon</title>
          <pubDate>Sun, 10 Mar 2019 22:00:00 +1000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/get-well-soon/</link>
          <guid>https://www.gridbugs.org/get-well-soon/</guid>
          <description xml:base="https://www.gridbugs.org/get-well-soon/">&lt;p&gt;Get Well Soon is a turn-based tactical dungeon crawler where all abilities are cards.
Maintain your deck as you fight through 6 procedurally-generated levels to
reach the bottom of the dungeon and reclaim what you have lost.&lt;&#x2F;p&gt;
&lt;p&gt;It is my entry in the 7 Day Roguelike game jam for 2019.&lt;&#x2F;p&gt;
&lt;p&gt;Play or download Get Well Soon on &lt;a href=&quot;https:&#x2F;&#x2F;gridbugs.itch.io&#x2F;get-well-soon&quot;&gt;its itch.io page&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;View the &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;gridbugs&#x2F;gws&#x2F;tree&#x2F;7drl&quot;&gt;source code on github&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;get-well-soon&#x2F;screenshot.png&quot; alt=&quot;screenshot.png&quot; &#x2F;&gt;&lt;&#x2F;p&gt;</description>
      </item>
      <item>
          <title>7 Day Roguelike 2019: Content, Plot, Polish, Publish</title>
          <pubDate>Sat, 09 Mar 2019 23:00:00 +1000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/7drl2019-day7/</link>
          <guid>https://www.gridbugs.org/7drl2019-day7/</guid>
          <description xml:base="https://www.gridbugs.org/7drl2019-day7/">&lt;p&gt;I spent all of Saturday adding content, fixing bugs, play-testing, and balancing gameplay.&lt;&#x2F;p&gt;
&lt;p&gt;There are now roughly 20 different cards and 3 different level generators.
I added a win condition, a little flavour text, and organised the game into a sequence
of 6 procedurally-generated levels, each configured to be more difficult than the last.&lt;&#x2F;p&gt;
&lt;p&gt;Then I spent about 4 hours play-testing and tweaking.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;7drl2019-day7&#x2F;glow.png&quot; alt=&quot;glow.png&quot; &#x2F;&gt;&lt;&#x2F;p&gt;</description>
      </item>
      <item>
          <title>7 Day Roguelike 2019: More Enemies, More Upgrades, More Cards</title>
          <pubDate>Fri, 08 Mar 2019 23:00:00 +1000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/7drl2019-day6/</link>
          <guid>https://www.gridbugs.org/7drl2019-day6/</guid>
          <description xml:base="https://www.gridbugs.org/7drl2019-day6/">&lt;p&gt;I added 2 new upgrades: the Cursed Altar, and the Plentiful Fountain.&lt;&#x2F;p&gt;
&lt;p&gt;The altar lets you take character upgrades, increasing max health, max power,
hand size, and vision distance. The altar also adds one negative card to your
deck. Negative cards take up precious space in your hand.&lt;&#x2F;p&gt;
&lt;p&gt;The fountain lets you add additional cards to your deck.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;7drl2019-day6&#x2F;eod.png&quot; alt=&quot;eod.png&quot; &#x2F;&gt;&lt;&#x2F;p&gt;</description>
      </item>
      <item>
          <title>7 Day Roguelike 2019: Character Progression</title>
          <pubDate>Thu, 07 Mar 2019 23:00:00 +1000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/7drl2019-day5/</link>
          <guid>https://www.gridbugs.org/7drl2019-day5/</guid>
          <description xml:base="https://www.gridbugs.org/7drl2019-day5/">&lt;p&gt;I added the first of three planned upgrade items tonight: the Cleansing Flame.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;7drl2019-day5&#x2F;progression.png&quot; alt=&quot;progression.png&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;In deck-building games, removing cards from your deck can improve your
deck as a whole, by increasing the odds of the remaining cards being drawn
at a critical moment. The Cleansing Flame upgrade lets you remove cards from your deck, at the
cost of health.&lt;&#x2F;p&gt;</description>
      </item>
      <item>
          <title>7 Day Roguelike 2019: Card Gameplay</title>
          <pubDate>Wed, 06 Mar 2019 23:00:00 +1000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/7drl2019-day4/</link>
          <guid>https://www.gridbugs.org/7drl2019-day4/</guid>
          <description xml:base="https://www.gridbugs.org/7drl2019-day4/">&lt;p&gt;As of tonight you can actually &lt;em&gt;play&lt;&#x2F;em&gt; the cards from your hand.
Select a card with the number keys. Most cards require a parameter
of some kind. For example the “Blink” card needs to be told which cell to blink into,
which you specify by moving a cursor into position and hitting enter.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;7drl2019-day4&#x2F;before.png&quot; alt=&quot;before.png&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;After blinking the game shows a glowing orb at your previous location, which
fades out after a few frames, just for fun!&lt;&#x2F;p&gt;</description>
      </item>
      <item>
          <title>7 Day Roguelike 2019: Card Display, Death Screen</title>
          <pubDate>Tue, 05 Mar 2019 22:00:00 +1000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/7drl2019-day3/</link>
          <guid>https://www.gridbugs.org/7drl2019-day3/</guid>
          <description xml:base="https://www.gridbugs.org/7drl2019-day3/">&lt;p&gt;Lots of work on rendering tonight. I made a renderer for the current hand, which can
display cards, empty spaces where cards used to be (before they were played), and
locked card slots which can be unlocked by character upgrade (not yet implemented!).
Deck building and actually playing the cards will come later. I want to have a solid
framework for displaying card info to players before I add too much content.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;7drl2019-day3&#x2F;death.png&quot; alt=&quot;death.png&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Also, who doesn’t love a good death screen? Hopefully by revealing the map to players
when they die, they will think about alternative choices they could have made
which might have lead to them living a little longer.
It could also tease at some undiscovered content!&lt;&#x2F;p&gt;</description>
      </item>
      <item>
          <title>7 Day Roguelike 2019: Physics and Improved Pathfinding</title>
          <pubDate>Mon, 04 Mar 2019 22:00:00 +1000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/7drl2019-day2/</link>
          <guid>https://www.gridbugs.org/7drl2019-day2/</guid>
          <description xml:base="https://www.gridbugs.org/7drl2019-day2/">&lt;p&gt;The most notable feature from tonight is NPCs pre-commit
to an action before the player takes their turn. This
means the game can telegraph enemy actions to the player,
which lets the player better plan ahead in combat.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;7drl2019-day2&#x2F;a.png&quot; alt=&quot;a.png&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;When an NPC is choosing where to move, it will avoid cells
which either currently contain an NPC, or which an NPC has
already committed to moving into. This does mean NPCs will tend
to be more spread out.&lt;&#x2F;p&gt;</description>
      </item>
      <item>
          <title>7 Day Roguelike 2019: Procgen and Basic Pathfinding</title>
          <pubDate>Sun, 03 Mar 2019 22:00:00 +1000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/7drl2019-day1/</link>
          <guid>https://www.gridbugs.org/7drl2019-day1/</guid>
          <description xml:base="https://www.gridbugs.org/7drl2019-day1/">&lt;p&gt;I configured WFC for generating spiky caves.
Much like staring at clouds, I often recognise shapes in the generated levels:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;7drl2019-day1&#x2F;1.png&quot; alt=&quot;1.png&quot; &#x2F;&gt;
&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;7drl2019-day1&#x2F;2.png&quot; alt=&quot;2.png&quot; &#x2F;&gt;
&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;7drl2019-day1&#x2F;3.png&quot; alt=&quot;3.png&quot; &#x2F;&gt;&lt;&#x2F;p&gt;</description>
      </item>
      <item>
          <title>7 Day Roguelike 2019: Planning and Preparation</title>
          <pubDate>Sun, 03 Mar 2019 09:00:00 +1000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/7drl2019-plan/</link>
          <guid>https://www.gridbugs.org/7drl2019-plan/</guid>
          <description xml:base="https://www.gridbugs.org/7drl2019-plan/">&lt;p&gt;I’m about to start working on my 7drl project for 2019: Get Well Soon.
It will be a deck-building, but otherwise traditional roguelike, in the sense that
the world is square grid, gameplay is turn-based, levels are procedurally generated
and you have to restart if you lose. All character abilities will be
activated by playing cards. The character can be upgraded by adding and removing
cards from the deck.&lt;&#x2F;p&gt;</description>
      </item>
      <item>
          <title>Procedural Generation with Wave Function Collapse</title>
          <pubDate>Thu, 21 Feb 2019 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/wave-function-collapse/</link>
          <guid>https://www.gridbugs.org/wave-function-collapse/</guid>
          <description xml:base="https://www.gridbugs.org/wave-function-collapse/">&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;wave-function-collapse&#x2F;flower-banner-scaled.png&quot; alt=&quot;flower-banner-scaled.png&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;div style=&quot;color:gray;font-style:italic&quot;&gt;
&lt;p&gt;(Edit 2022-05-03: I found out that the Wave Function Collapse algorithm was heavily inspired by
an existing algorithm called “Model Synthesis”. I’ve added a &lt;a href=&quot;#model-synthesis&quot;&gt;section&lt;&#x2F;a&gt; to
further reading with links to the author’s website for more information.)&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
&lt;p&gt;Wave Function Collapse is a procedural generation algorithm which produces
images by arranging a collection of tiles according to rules about which tiles
may be adjacent to each other tile, and relatively how frequently each tile should appear.
The algorithm maintains, for each pixel of the output image, a probability
distribution of the tiles which may be placed there. It repeatedly chooses a
pixel to “collapse” - choosing a tile to use for that pixel based on its
distribution. WFC gets its name from
&lt;a href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;Wave_function_collapse&quot;&gt;quantum physics&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;The goal of this post is to build an intuition for how and why the WFC algorithm works.&lt;&#x2F;p&gt;</description>
      </item>
      <item>
          <title>2018 Recap</title>
          <pubDate>Sun, 30 Dec 2018 00:00:00 +0000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/2018-recap/</link>
          <guid>https://www.gridbugs.org/2018-recap/</guid>
          <description xml:base="https://www.gridbugs.org/2018-recap/">&lt;p&gt;Here’s a summary of the things I made this year - mostly libraries and
experiments relating to games and computer graphics. Many of these projects
deserve a dedicated post explaining them in further depth, and I intend to
elaborate further in future posts. All these projets are written in rust.
Headings link to the projects on github. All animations are realtime screen
recordings.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;wave-function-collapse-library&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#wave-function-collapse-library&quot; aria-label=&quot;Anchor link for: wave-function-collapse-library&quot;&gt;&lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;gridbugs&#x2F;wfc&quot;&gt;Wave Function Collapse Library&lt;&#x2F;a&gt;&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;2018-recap&#x2F;wfc-cat.png&quot; alt=&quot;wfc-cat.png&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Wave Function Collapse is a procedural generation algorithm which takes as input
a list of tiles, rules describing which tiles may appear adjacent to one another
in the output, and the relative frequency with which each tile should appear in
the output. A common way of specifying this input is in the form of an example
image which looks similar to the desired output.&lt;&#x2F;p&gt;</description>
      </item>
      <item>
          <title>Meters Below the Ground</title>
          <pubDate>Sat, 10 Mar 2018 11:30:00 +1000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/meters-below-the-ground/</link>
          <guid>https://www.gridbugs.org/meters-below-the-ground/</guid>
          <description xml:base="https://www.gridbugs.org/meters-below-the-ground/">&lt;p&gt;Meters Below the Ground is a short tactical dungeon-crawler about escaping from
an insectoid-infested facility. Complete objectives to unlock more meters for
your character so you might stand a chance against the alien threat.&lt;&#x2F;p&gt;
&lt;p&gt;It is my entry in the 7 Day Roguelike game jam for 2018.&lt;&#x2F;p&gt;
&lt;p&gt;Play or download Meters Below the Ground in &lt;a href=&quot;https:&#x2F;&#x2F;gridbugs.itch.io&#x2F;meters-below-the-ground&quot;&gt;its itch.io page&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;View the &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;gridbugs&#x2F;meters-below-the-ground&quot;&gt;source code on github&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;meters-below-the-ground&#x2F;screenshot.png&quot; alt=&quot;screenshot.png&quot; &#x2F;&gt;&lt;&#x2F;p&gt;</description>
      </item>
      <item>
          <title>7 Day Roguelike 2018: Success</title>
          <pubDate>Sat, 10 Mar 2018 00:37:00 +1000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/7drl2018-success/</link>
          <guid>https://www.gridbugs.org/7drl2018-success/</guid>
          <description xml:base="https://www.gridbugs.org/7drl2018-success/">&lt;p&gt;The game is done! I’ll make another post in a day or so with links to download
and play the game, but if you can’t wait, &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;gridbugs&#x2F;meters-below-the-ground&quot;&gt;get the source code from github&lt;&#x2F;a&gt; or
&lt;a href=&quot;https:&#x2F;&#x2F;gridbugs.itch.io&#x2F;meters-below-the-ground&quot;&gt;play or download the game from its itch.io page&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;</description>
      </item>
      <item>
          <title>7 Day Roguelike 2018: Meters, Objectives</title>
          <pubDate>Fri, 09 Mar 2018 00:39:00 +1000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/7drl2018-meters-objectives/</link>
          <guid>https://www.gridbugs.org/7drl2018-meters-objectives/</guid>
          <description xml:base="https://www.gridbugs.org/7drl2018-meters-objectives/">&lt;p&gt;Tonight I added new meters and objectives.&lt;&#x2F;p&gt;
&lt;p&gt;In this screenshot, the player has just activated an emergency beacon - the objective
of the level. Doing so alerts all NPCs of the player’s presence, so getting to the
stairs will be challenging.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;7drl2018-meters-objectives&#x2F;beacon.png&quot; alt=&quot;beacon.png&quot; &#x2F;&gt;&lt;&#x2F;p&gt;</description>
      </item>
      <item>
          <title>7 Day Roguelike 2018: NPCs</title>
          <pubDate>Thu, 08 Mar 2018 00:01:00 +1000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/7drl2018-npcs/</link>
          <guid>https://www.gridbugs.org/7drl2018-npcs/</guid>
          <description xml:base="https://www.gridbugs.org/7drl2018-npcs/">&lt;p&gt;Tonight I added all the NPCs! I’ve followed an insect theme, and NPCs transform
based on an insect lifecycle. Levels begin populated with eggs and larvae.
Eventually, the eggs hatch into more larvae, and the larvae turn into chrysalises.
The chrysalises hatch into either an arachnoid - a fast enemy, which takes 2 turns
for every turn the player takes - or a beetoid, which acts at normal speed, but
has more health. There’s also a super egg which hatches into a queen, which I’ll
use for missions.&lt;&#x2F;p&gt;</description>
      </item>
      <item>
          <title>7 Day Roguelike 2018: Character Progression, Melee Combat</title>
          <pubDate>Tue, 06 Mar 2018 23:44:00 +1000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/7drl2018-character-progression-melee-combat/</link>
          <guid>https://www.gridbugs.org/7drl2018-character-progression-melee-combat/</guid>
          <description xml:base="https://www.gridbugs.org/7drl2018-character-progression-melee-combat/">&lt;p&gt;Today I implemented character progression. Character progression is entirely
made up of meters, which either give you new abilities, or passive benefits.
You get to choose 1 of 3 randomly selected meters to add to your character at
the end of each level, but only if you complete the mission for that level.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;7drl2018-character-progression-melee-combat&#x2F;upgrades.png&quot; alt=&quot;upgrades.png&quot; &#x2F;&gt;&lt;&#x2F;p&gt;</description>
      </item>
      <item>
          <title>7 Day Roguelike 2018: Items, Glossary, Combat, Railgun</title>
          <pubDate>Mon, 05 Mar 2018 23:42:00 +1000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/7drl2018-items-glossary-combat-railgun/</link>
          <guid>https://www.gridbugs.org/7drl2018-items-glossary-combat-railgun/</guid>
          <description xml:base="https://www.gridbugs.org/7drl2018-items-glossary-combat-railgun/">&lt;p&gt;The most exciting changes today are the “Glossary”, and a revamped combat
system.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;7drl2018-items-glossary-combat-railgun&#x2F;screenshot.png&quot; alt=&quot;screenshot.png&quot; &#x2F;&gt;&lt;&#x2F;p&gt;</description>
      </item>
      <item>
          <title>7 Day Roguelike 2018: Procgen, Visibility, Missions, Colour</title>
          <pubDate>Sun, 04 Mar 2018 22:16:00 +1000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/7drl2018-procgen-visibility-missions-colour/</link>
          <guid>https://www.gridbugs.org/7drl2018-procgen-visibility-missions-colour/</guid>
          <description xml:base="https://www.gridbugs.org/7drl2018-procgen-visibility-missions-colour/">&lt;p&gt;Lots of progress today! It’s starting to look like a game.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;7drl2018-procgen-visibility-missions-colour&#x2F;screenshot.png&quot; alt=&quot;screenshot.png&quot; &#x2F;&gt;&lt;&#x2F;p&gt;</description>
      </item>
      <item>
          <title>7 Day Roguelike 2018: Day 1</title>
          <pubDate>Sun, 04 Mar 2018 00:47:00 +1000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/7drl2018-day1/</link>
          <guid>https://www.gridbugs.org/7drl2018-day1/</guid>
          <description xml:base="https://www.gridbugs.org/7drl2018-day1/">&lt;p&gt;This year I’m making a traditional roguelike where all of your stats, abilities
and goals are expressed via progress meters. The name of the game will be
“Meters Below the Ground”. Here’s an early screenshot:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;7drl2018-day1&#x2F;screenshot.png&quot; alt=&quot;screenshot.png&quot; &#x2F;&gt;&lt;&#x2F;p&gt;</description>
      </item>
      <item>
          <title>Pathfinding on a Grid</title>
          <pubDate>Thu, 15 Feb 2018 21:43:00 +1000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/pathfinding-on-a-grid/</link>
          <guid>https://www.gridbugs.org/pathfinding-on-a-grid/</guid>
          <description xml:base="https://www.gridbugs.org/pathfinding-on-a-grid/">&lt;p&gt;In this post I describe how to achieve sensible pathfinding of multiple
NPCs in a turn-based, grid-based setting.
When multiple NPCs are moving around on the same map, they can get in each
other’s way, which causes unusual-looking behaviour unless properly handled.
This post builds up a pathfinding technique, identifying, and addressing,
unusual behaviour caused by multiple NPCs interfering with each other.&lt;&#x2F;p&gt;
&lt;p&gt;I’ll make the
following assumptions:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;there is a single player&lt;&#x2F;li&gt;
&lt;li&gt;the goal of each NPC is to become adjacent to the player&lt;&#x2F;li&gt;
&lt;li&gt;NPCs can’t move through one another or the player&lt;&#x2F;li&gt;
&lt;li&gt;on their turn, the player, and each NPC can move 1 or 0 squares in a cardinal direction&lt;&#x2F;li&gt;
&lt;li&gt;the map is a 2d grid of squares, each of which is permanently either
traversable or non-traversable&lt;&#x2F;li&gt;
&lt;li&gt;the cost of moving between a pair of adjacent, traversable squares is 1 (ie. the grid is
uniform)&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;The approach I describe won’t rely too heavily on any of these assumptions.
I’m stating them here so as to remove any ambiguity in explanations.
Generalising the pathfinding technique to rely on fewer assumptions is left as
an exercise to the reader.&lt;&#x2F;p&gt;
&lt;p&gt;A note on diagrams: The player will be denoted with a &lt;code&gt;@&lt;&#x2F;code&gt;. Each NPC will be
referred to by a letter. White cells are traversable. Grey cells are
non-traversable.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;initial-approach-follow-a-dijkstra-map&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#initial-approach-follow-a-dijkstra-map&quot; aria-label=&quot;Anchor link for: initial-approach-follow-a-dijkstra-map&quot;&gt;Initial Approach: Follow a Dijkstra Map&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;The first approach will be to generate a map which stores, for each cell, the
distance from that cell to the player. These distances are shown as the numbers
in the top-left corner of each traversable cell in the diagrams below.
The data structure storing these distances (generally a 2d array of numbers) is
sometimes called a “Dijkstra Map”.
&lt;a href=&quot;http:&#x2F;&#x2F;www.roguebasin.com&#x2F;index.php?title=The_Incredible_Power_of_Dijkstra_Maps&quot;&gt;Here&lt;&#x2F;a&gt;
is an article elaborating on dijkstra maps.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;pathfinding-on-a-grid&#x2F;a.png&quot; alt=&quot;a.png&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;A benefit of using a dijkstra map, rather than searching for a path for each NPC,
is that the dijkstra map only needs to be recomputed each time the player moves.
To determine which way an NPC should move, we need only consider the neighbours
of that NPC’s cell in the dijkstra map, choosing the one with the lowest score.
This scales well as the number of NPCs increases, as there is only a small
amount of work that must be done for each NPC.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;pathfinding-on-a-grid&#x2F;b.png&quot; alt=&quot;b.png&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;The example above shows a single step for &lt;code&gt;X&lt;&#x2F;code&gt; and &lt;code&gt;Y&lt;&#x2F;code&gt;, where each moved to its
lowest-valued neighbour.
Note that &lt;code&gt;Y&lt;&#x2F;code&gt; chose arbitrarily between 2 cells equidistant from the player.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;gaps-forming-between-npcs&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#gaps-forming-between-npcs&quot; aria-label=&quot;Anchor link for: gaps-forming-between-npcs&quot;&gt;Gaps forming between NPCs&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;Here’s the first problem with the current approach. Consider the following
arrangement:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;pathfinding-on-a-grid&#x2F;c.png&quot; alt=&quot;c.png&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;When &lt;code&gt;X&lt;&#x2F;code&gt; moves:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;pathfinding-on-a-grid&#x2F;d.png&quot; alt=&quot;d.png&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;And then &lt;code&gt;Y&lt;&#x2F;code&gt; moves:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;pathfinding-on-a-grid&#x2F;e.png&quot; alt=&quot;e.png&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Everything seems fine, but what if &lt;code&gt;Y&lt;&#x2F;code&gt; had moved first? Since NPCs can’t move
through each other, the best choice for &lt;code&gt;Y&lt;&#x2F;code&gt; is to stay where it is, as any
single legal move would only increase its distance from the player.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;pathfinding-on-a-grid&#x2F;c.png&quot; alt=&quot;c.png&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;On &lt;code&gt;X&lt;&#x2F;code&gt;’s
turn, &lt;code&gt;X&lt;&#x2F;code&gt; would still move towards the player, creating a gap between &lt;code&gt;X&lt;&#x2F;code&gt; and
&lt;code&gt;Y&lt;&#x2F;code&gt;, which looks unusual:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;pathfinding-on-a-grid&#x2F;d.png&quot; alt=&quot;d.png&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;To prevent gaps from forming, we need to make sure that
NPCs move in increasing order of their distance to the player (that is,
the closest NPC to the player moves first, then the second closest, and so on). Since we generate
a dijkstra map, it’s easy to determine how far each NPC is from the player.
Simply sorting NPCs by the value of their cell in the dijkstra map is enough to
produce a turn order which avoids this problem.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;npcs-getting-in-each-other-s-way&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#npcs-getting-in-each-other-s-way&quot; aria-label=&quot;Anchor link for: npcs-getting-in-each-other-s-way&quot;&gt;NPCs getting in each other’s way&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;Let’s look at some cases where NPCs interfere with the movement of other NPCs.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;pathfinding-on-a-grid&#x2F;e.png&quot; alt=&quot;e.png&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;On &lt;code&gt;X&lt;&#x2F;code&gt;’s next turn, it will remain next to the player - there’s nothing it can
do to get closer to the player, so there’s no need to move. (In a typical roguelike it
would probably attack the player at this point.) What does &lt;code&gt;Y&lt;&#x2F;code&gt; do? None of
&lt;code&gt;Y&lt;&#x2F;code&gt;’s traversable neighbours are lower-valued in the dijkstra map than &lt;code&gt;Y&lt;&#x2F;code&gt;’s
current cell, so it will stay put, despite it being possible to eventually get
closer to the &lt;code&gt;@&lt;&#x2F;code&gt;, by first moving north, into a higher-valued cell.&lt;&#x2F;p&gt;
&lt;p&gt;In the next example, assume &lt;code&gt;X&lt;&#x2F;code&gt; moves before &lt;code&gt;Y&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;pathfinding-on-a-grid&#x2F;g.png&quot; alt=&quot;g.png&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;code&gt;X&lt;&#x2F;code&gt; will move east, occupying &lt;code&gt;Y&lt;&#x2F;code&gt;’s west neighbour. On &lt;code&gt;Y&lt;&#x2F;code&gt;’s turn, its
valid moves are to move east, into a higher-valued cell than its present cell,
or to do nothing. If &lt;code&gt;Y&lt;&#x2F;code&gt;’s goal is to get to the player in as few turns as
possible, its best course of action is to wait where it is for one turn,
assuming &lt;code&gt;X&lt;&#x2F;code&gt; will proceed south and make room for &lt;code&gt;Y&lt;&#x2F;code&gt; to continue moving towards
the player. &lt;code&gt;Y&lt;&#x2F;code&gt;’s alternative - moving east, and proceeding down the east edge
of the map - will take longer.&lt;&#x2F;p&gt;
&lt;p&gt;What if there’s a gap in the wall closer to &lt;code&gt;Y&lt;&#x2F;code&gt;. Assume &lt;code&gt;X&lt;&#x2F;code&gt; moves first again:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;pathfinding-on-a-grid&#x2F;n.png&quot; alt=&quot;n.png&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Here, it might make sense for &lt;code&gt;Y&lt;&#x2F;code&gt; to move east on its turn rather than waiting a
turn before moving west. It may look more natural for &lt;code&gt;Y&lt;&#x2F;code&gt; to follow a longer
path, than wait for the shorter one to free up, though the decision
seems to depend on how much longer the longer path is.&lt;&#x2F;p&gt;
&lt;p&gt;Assume it’s &lt;code&gt;Z&lt;&#x2F;code&gt;’s turn. What should it do?&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;pathfinding-on-a-grid&#x2F;o.png&quot; alt=&quot;o.png&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Here there’s no path to the player, but rather than giving up, &lt;code&gt;Z&lt;&#x2F;code&gt; should
probably still try to get as close to the player as it can.
It seems like it should move east, south, south, on its next 3 turns,
assuming nothing else moves.&lt;&#x2F;p&gt;
&lt;p&gt;After making the first move it finds itself here:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;pathfinding-on-a-grid&#x2F;p.png&quot; alt=&quot;p.png&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Looking at its immediate neighbours, it seems like &lt;code&gt;Z&lt;&#x2F;code&gt; could now go either
south, or west - the neighbours in both directions are the same distance from
the player. It is of
course preferable for &lt;code&gt;Z&lt;&#x2F;code&gt; to move south.&lt;&#x2F;p&gt;
&lt;p&gt;This last case is important, as it shows that
the presence of multiple NPCs means that it’s never
sufficient for an NPC to act purely on its neighbouring cells in the game grid
and dijkstra map.&lt;&#x2F;p&gt;
&lt;p&gt;A solution which addresses all these cases, is for each NPC, on is turn, to
examine all the cells reachable within a certain number of turns (3 turns will
suffice for all the examples here), and take the first step along the shortest
path to the cell it just examined with the lowest value in the dijkstra map.
If all the cells examined have a higher value than the NPC’s current cell, it
doesn’t move at all.&lt;&#x2F;p&gt;
&lt;p&gt;It’s worth noting that this won’t necessarily get every NPC as close
to the player as possible.&lt;&#x2F;p&gt;
&lt;p&gt;Will &lt;code&gt;Z&lt;&#x2F;code&gt; go east or west, looking for the best cell up to 3 moves away?&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;pathfinding-on-a-grid&#x2F;r.png&quot; alt=&quot;r.png&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;It could go either way! If it had looked 4 moves away, it would have
noticed that going west gives the potential to eventually be 1 move from the
player, while going east, the closest it can get is 2 away. Note however, that in
this example, &lt;code&gt;W&lt;&#x2F;code&gt; will move first (it’s closer to the player than &lt;code&gt;Z&lt;&#x2F;code&gt;), and &lt;code&gt;W&lt;&#x2F;code&gt;
is close enough to notice that going west is better, so it will go west, getting
out of &lt;code&gt;Z&lt;&#x2F;code&gt;’s way and allowing &lt;code&gt;Z&lt;&#x2F;code&gt; to move forward.&lt;&#x2F;p&gt;
&lt;p&gt;To improve an NPC’s decision making, you can increase the number of cells it
examines before moving.
The specific number depends on the nature of the game and
the individual NPC. In typical games, where NPCs die and the player moves
around, sub-optimal-but-still-pretty-good decision-making by NPCs won’t have a
noticeable effect on gameplay.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;summary&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#summary&quot; aria-label=&quot;Anchor link for: summary&quot;&gt;Summary&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;Before any NPCs move, if the player has moved since the dijkstra map was last
computed, recompute the dijkstra map based on the player’s current position.
Now, produce a list of NPCs sorted by the
value of their cells in the dijkstra map, in increasing order.
NPCs will take their turns in this order.
On each NPC’s turn, examine all the cells within a certain number of moves from
the NPC, without allowing moves through other NPCs.
Of the cells examined, identify the one whose value in the dijkstra map
is lowest. If no cell has a lower value than the NPC’s current cell,
the NPC doesn’t move that turn. Otherwise, the NPC takes the first
step along the shortest path between its
current cell and this cell. This causes NPCs to move towards the player in a
sensible way, without the influence of other NPCs causing unusual-looking
behaviour.&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Another Roguelike Lighting Demo</title>
          <pubDate>Sun, 10 Dec 2017 17:37:00 +1000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/another-roguelike-lighting-demo/</link>
          <guid>https://www.gridbugs.org/another-roguelike-lighting-demo/</guid>
          <description xml:base="https://www.gridbugs.org/another-roguelike-lighting-demo/">&lt;p&gt;In keeping with my habit of making game engines rather than entire games, here’s
a demo that was originally meant to be a game, but I got sidetracked by making
it pretty and lost momentum.
&lt;a href=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;post-mortem-of-abandoned-game&#x2F;&quot;&gt;This has happened before.&lt;&#x2F;a&gt; It’s just another
step on the path to enlightenment. This post describes some of the new things I
tried.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;another-roguelike-lighting-demo&#x2F;screenshot.png&quot; alt=&quot;screenshot.png&quot; &#x2F;&gt;&lt;&#x2F;p&gt;</description>
      </item>
      <item>
          <title>Modifying Entity Component System for Turn-Based Games</title>
          <pubDate>Sat, 01 Apr 2017 19:28:01 +1000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/modifying-entity-component-system-for-turn-based-games/</link>
          <guid>https://www.gridbugs.org/modifying-entity-component-system-for-turn-based-games/</guid>
          <description xml:base="https://www.gridbugs.org/modifying-entity-component-system-for-turn-based-games/">&lt;p&gt;This article describes my modifications to the
&lt;a href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;Entity%E2%80%93component%E2%80%93system&quot;&gt;Entity Component System (ECS)&lt;&#x2F;a&gt;
architecture pattern to better support a turn-based game loop.
This involves implementing game logic in &lt;strong&gt;actions&lt;&#x2F;strong&gt; which describe
changes to the game’s state, and &lt;strong&gt;rules&lt;&#x2F;strong&gt; which prevent certain actions, and
trigger additional reactions. The combination of actions and rules replace the
traditional idea of &lt;strong&gt;systems&lt;&#x2F;strong&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;I implemented these changes in the engine I used for my 7DRL: &lt;a href=&quot;https:&#x2F;&#x2F;gridbugs.itch.io&#x2F;apocalypse-post&quot;&gt;Apocalypse
Post&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;entity-component-systems&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#entity-component-systems&quot; aria-label=&quot;Anchor link for: entity-component-systems&quot;&gt;Entity Component Systems&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;&lt;strong&gt;Entities&lt;&#x2F;strong&gt; are objects in the game world. Each entity has a collection of
&lt;strong&gt;components&lt;&#x2F;strong&gt; that define what that entity is. A component is a piece of typed
data. All the data making up the game state is in the form of components, each
belonging to exactly one entity. Some example components:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;a &lt;strong&gt;position&lt;&#x2F;strong&gt; which stores the location of an entity&lt;&#x2F;li&gt;
&lt;li&gt;a &lt;strong&gt;velocity&lt;&#x2F;strong&gt; which stores the speed and direction in which an entity is
moving&lt;&#x2F;li&gt;
&lt;li&gt;a &lt;strong&gt;solid&lt;&#x2F;strong&gt; flag, which denotes an entity as being solid for the purposes of
collision detection&lt;&#x2F;li&gt;
&lt;li&gt;a &lt;strong&gt;tile&lt;&#x2F;strong&gt; which tells the renderer how to draw a component&lt;&#x2F;li&gt;
&lt;li&gt;a &lt;strong&gt;controlled&lt;&#x2F;strong&gt; flag which denotes that this entity is controlled by the
player&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;All game logic is implemented in the form of &lt;strong&gt;systems&lt;&#x2F;strong&gt;. Each system is
interested in a particular set of components. Typically, systems are described
as running continuously, or having periodic ticks, where they iterate over all
the entities that possess its interested set of components, and performing some
system-specific operation. Some example systems:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;movement:&lt;&#x2F;strong&gt; For each entity with a &lt;strong&gt;position&lt;&#x2F;strong&gt; and &lt;strong&gt;velocity&lt;&#x2F;strong&gt;, move the entity by
changing its &lt;strong&gt;position&lt;&#x2F;strong&gt; based on its &lt;strong&gt;velocity&lt;&#x2F;strong&gt;.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;collision:&lt;&#x2F;strong&gt; For each entity with a &lt;strong&gt;position&lt;&#x2F;strong&gt;, &lt;strong&gt;velocity&lt;&#x2F;strong&gt; and &lt;strong&gt;solid&lt;&#x2F;strong&gt;, if it
attempted to move through another entity with &lt;strong&gt;solid&lt;&#x2F;strong&gt;, apply some collision
resolution policy.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;input:&lt;&#x2F;strong&gt; If a button is currently pressed, corresponding to some
game control, apply the effect of that control to each entity with a
&lt;strong&gt;controlled&lt;&#x2F;strong&gt; component.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;renderer:&lt;&#x2F;strong&gt; For each entity with a &lt;strong&gt;position&lt;&#x2F;strong&gt; and a &lt;strong&gt;tile&lt;&#x2F;strong&gt;, draw the
image described by the entity’s &lt;strong&gt;tile&lt;&#x2F;strong&gt; at a location on the screen based on
the entity’s &lt;strong&gt;position&lt;&#x2F;strong&gt;.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;game-loops&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#game-loops&quot; aria-label=&quot;Anchor link for: game-loops&quot;&gt;Game Loops&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;Here’s a straw-person implementation for the game loop of an ECS game engine.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;javascript&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-javascript &quot;&gt;&lt;code class=&quot;language-javascript&quot; data-lang=&quot;javascript&quot;&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;function &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;game_loop&lt;&#x2F;span&gt;&lt;span&gt;(game_state) {
&lt;&#x2F;span&gt;&lt;span&gt;    forever {
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;        time_delta &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;wait_for_frame&lt;&#x2F;span&gt;&lt;span&gt;();
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;        for each system &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;in &lt;&#x2F;span&gt;&lt;span&gt;systems {
&lt;&#x2F;span&gt;&lt;span&gt;            system.&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;tick&lt;&#x2F;span&gt;&lt;span&gt;(game_state, time_delta);
&lt;&#x2F;span&gt;&lt;span&gt;        }
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This is perhaps an over-simplification, though most of the literature I’ve read
about ECS describes something resembling that game loop. I claim that this game
loop is more suited for real-time games than turn-based games.&lt;&#x2F;p&gt;
&lt;p&gt;In a real-time
game, you must constantly re-render the scene so the player can see changes to
the game’s state. At any point, the player may press button, and the game state
must update immediately. Physics is constantly being enforced, non-player
characters are
constantly determining what to do next, animations are always being played.
Everything notionally happens at once, all the time, so the idea of a periodic
tick makes sense.&lt;&#x2F;p&gt;
&lt;p&gt;In a turn-based game, each character (player or non-player) acts on their turn.
Typically, they perform a single action, which may have some follow-on actions,
and then it’s the next player’s turn. The scene only needs to be rendered after
the state of the game has changed.&lt;&#x2F;p&gt;
&lt;p&gt;For my game, I wanted to have a game loop resembling:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;javascript&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-javascript &quot;&gt;&lt;code class=&quot;language-javascript&quot; data-lang=&quot;javascript&quot;&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;function &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;game_loop&lt;&#x2F;span&gt;&lt;span&gt;(game_state) {
&lt;&#x2F;span&gt;&lt;span&gt;    forever {
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;* Figure out whose turn it is. *&#x2F;
&lt;&#x2F;span&gt;&lt;span&gt;        current_character &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span&gt;schedule.&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;get_next_character&lt;&#x2F;span&gt;&lt;span&gt;();
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;* The character declares an action they will take.
&lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;         * This blocks waiting for input if it&amp;#39;s the player&amp;#39;s turn.
&lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;         * Otherwise the AI for the character is invoked. *&#x2F;
&lt;&#x2F;span&gt;&lt;span&gt;        action &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span&gt;current_character.&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;determine_action&lt;&#x2F;span&gt;&lt;span&gt;();
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;* If the rules permit the action... *&#x2F;
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;if &lt;&#x2F;span&gt;&lt;span&gt;rules.&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;permit&lt;&#x2F;span&gt;&lt;span&gt;(action) {
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;* ...then actually do the action. *&#x2F;
&lt;&#x2F;span&gt;&lt;span&gt;            game_state.&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;commit&lt;&#x2F;span&gt;&lt;span&gt;(action);
&lt;&#x2F;span&gt;&lt;span&gt;        }
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;* Schedule the character&amp;#39;s next turn. *&#x2F;
&lt;&#x2F;span&gt;&lt;span&gt;        schedule.&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;insert&lt;&#x2F;span&gt;&lt;span&gt;(current_character);
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;* Finally, render the scene. *&#x2F;
&lt;&#x2F;span&gt;&lt;span&gt;        renderer.&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;render&lt;&#x2F;span&gt;&lt;span&gt;(game_state);
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Again, this is over-simplified. The key points are:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;I want the ability to block waiting for input player’s turn, rather than
periodically sampling input. The motivation for this is power-saving.&lt;&#x2F;li&gt;
&lt;li&gt;I only want to re-draw the scene when necessary, rather than periodically.
This is also to save power.&lt;&#x2F;li&gt;
&lt;li&gt;I want the ability to reason about the outcome of an action before it is
committed.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;These goals are incompatible with the idea of &lt;strong&gt;systems&lt;&#x2F;strong&gt; as they are typically
described in ECS literature. In my engine, I implement game logic in
&lt;strong&gt;actions&lt;&#x2F;strong&gt;, which describe changes to the game state, and &lt;strong&gt;rules&lt;&#x2F;strong&gt;, which
describe restrictions on which actions can be committed, as well as follow-on
action which happen in response to certain actions. In the remainder of this
post, I’ll describe how actions and rules work.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;storing-data-entities-and-components&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#storing-data-entities-and-components&quot; aria-label=&quot;Anchor link for: storing-data-entities-and-components&quot;&gt;Storing Data: Entities and Components&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;These are unchanged from the traditional ECS pattern, but I’ll introduce my
implementation of them to simplify the explanation of new concepts later.&lt;&#x2F;p&gt;
&lt;p&gt;I consider two kinds of component:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Data Components&lt;&#x2F;strong&gt; store typed data about an entity.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Flag Components&lt;&#x2F;strong&gt; store no data, but their presence in an entity is
meaningful.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;An entity is represented by a unique identifier - namely, a 64-bit integer.
For each type of component, there is a single data structure which stores
values of that component for all entities. For data components, values are
stored in a hash table, keyed by entity id. If an entity has a particular data
component, that component’s value will be stored against the entity’s id in that
component’s hash table. For each flag component, there is a
set of entity ids, such that if an entity’s id is in the set, then that entity
is considered to have that component.&lt;&#x2F;p&gt;
&lt;p&gt;Here’s an example entity store:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;type &lt;&#x2F;span&gt;&lt;span&gt;EntityId &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= u64&lt;&#x2F;span&gt;&lt;span&gt;;
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;struct &lt;&#x2F;span&gt;&lt;span&gt;EntityStore {
&lt;&#x2F;span&gt;&lt;span&gt;    position: HashMap&amp;lt;EntityId, (&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;isize&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;isize&lt;&#x2F;span&gt;&lt;span&gt;)&amp;gt;,
&lt;&#x2F;span&gt;&lt;span&gt;    door_state: HashMap&amp;lt;EntityId, DoorState&amp;gt;,
&lt;&#x2F;span&gt;&lt;span&gt;    tile: HashMap&amp;lt;EntityId, TileType&amp;gt;,
&lt;&#x2F;span&gt;&lt;span&gt;    solid: HashSet&amp;lt;EntityId&amp;gt;,
&lt;&#x2F;span&gt;&lt;span&gt;    can_open_doors: HashSet&amp;lt;EntityId&amp;gt;,
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; supporting types
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;enum &lt;&#x2F;span&gt;&lt;span&gt;DoorState {
&lt;&#x2F;span&gt;&lt;span&gt;    Open,
&lt;&#x2F;span&gt;&lt;span&gt;    Closed,
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;enum &lt;&#x2F;span&gt;&lt;span&gt;TileType {
&lt;&#x2F;span&gt;&lt;span&gt;    OpenDoor,
&lt;&#x2F;span&gt;&lt;span&gt;    ClosedDoor,
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; getters
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;impl &lt;&#x2F;span&gt;&lt;span&gt;EntityStore {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;get_position&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;self, id: EntityId) -&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Option&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;(&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;isize&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;isize&lt;&#x2F;span&gt;&lt;span&gt;)&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;        self.position.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;get&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;id).&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;map&lt;&#x2F;span&gt;&lt;span&gt;(|v| &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;*&lt;&#x2F;span&gt;&lt;span&gt;v)
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; repeated for each data component
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;contains_solid&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;self, id: EntityId) -&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;bool &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;        self.solid.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;contains&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;id)
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; repeated for each flag component
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h2 id=&quot;mutating-data-actions&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#mutating-data-actions&quot; aria-label=&quot;Anchor link for: mutating-data-actions&quot;&gt;Mutating Data: Actions&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;An action describes a change to the game state. There are a small number of
ways the game state can be changed:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;the value of an entity’s data component can be set (added or changed)&lt;&#x2F;li&gt;
&lt;li&gt;an entity can gain a new flag component&lt;&#x2F;li&gt;
&lt;li&gt;an entity can lose a component&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Note that the first two types of change both correspond to an entry being added
to a component store. Also note that I only talk about components -
not entities. There is no global list of entities, and no explicit way to add or
remove entities. Adding an entity is equivalent to adding some components with
the new entity’s id. Removing an entity is equivalent to removing the entries
from all component stores with the entity’s id.&lt;&#x2F;p&gt;
&lt;p&gt;An action is represented by an &lt;code&gt;EntityStore&lt;&#x2F;code&gt; (defined above), storing all
component values being added or changed by the action. Additionally, for each
component type, an action has a set of entity id’s that are losing that
component.&lt;&#x2F;p&gt;
&lt;p&gt;Example implementation:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;struct &lt;&#x2F;span&gt;&lt;span&gt;RemovedComponents {
&lt;&#x2F;span&gt;&lt;span&gt;    position: HashSet&amp;lt;EntityId&amp;gt;,
&lt;&#x2F;span&gt;&lt;span&gt;    tile: HashSet&amp;lt;EntityId&amp;gt;,
&lt;&#x2F;span&gt;&lt;span&gt;    door_state: HashSet&amp;lt;EntityId&amp;gt;,
&lt;&#x2F;span&gt;&lt;span&gt;    tile: HashSet&amp;lt;EntityId&amp;gt;,
&lt;&#x2F;span&gt;&lt;span&gt;    solid: HashSet&amp;lt;EntityId&amp;gt;,
&lt;&#x2F;span&gt;&lt;span&gt;    can_open_doors: HashSet&amp;lt;EntityId&amp;gt;,
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;struct &lt;&#x2F;span&gt;&lt;span&gt;Action {
&lt;&#x2F;span&gt;&lt;span&gt;    additions: EnityStore,
&lt;&#x2F;span&gt;&lt;span&gt;    removals: RemovedComponents,
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;impl &lt;&#x2F;span&gt;&lt;span&gt;Action {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;pub fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;remove_position&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut &lt;&#x2F;span&gt;&lt;span&gt;self, id: EntityId) { &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;... &lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; repeated for each component
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;pub fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;insert_position&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut &lt;&#x2F;span&gt;&lt;span&gt;self, id: EntityID,
&lt;&#x2F;span&gt;&lt;span&gt;                           value: (isize, isize)) { &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;... &lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; repeated for each data component
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;pub fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;insert_solid&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut &lt;&#x2F;span&gt;&lt;span&gt;self, id: EntityId) { &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;... &lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; repeated for each flag component
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The engine also needs a way to commit actions:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; applies `action` to `state`, clearing `action` in the process
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;commit_action&lt;&#x2F;span&gt;&lt;span&gt;(state: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut&lt;&#x2F;span&gt;&lt;span&gt; EntityStore, action: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut&lt;&#x2F;span&gt;&lt;span&gt; Action) {
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; removals
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;for&lt;&#x2F;span&gt;&lt;span&gt; id &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;in&lt;&#x2F;span&gt;&lt;span&gt; action.removals.position.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;drain&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;..&lt;&#x2F;span&gt;&lt;span&gt;) {
&lt;&#x2F;span&gt;&lt;span&gt;        state.position.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;remove&lt;&#x2F;span&gt;&lt;span&gt;(id);
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; repeated for each component type
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; data insertions
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;for &lt;&#x2F;span&gt;&lt;span&gt;(id, value) &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;in&lt;&#x2F;span&gt;&lt;span&gt; action.insertions.position.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;drain&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;..&lt;&#x2F;span&gt;&lt;span&gt;) {
&lt;&#x2F;span&gt;&lt;span&gt;        state.position.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;insert&lt;&#x2F;span&gt;&lt;span&gt;(id, value);
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; repeated for each data component type
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; flag insertions
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;for&lt;&#x2F;span&gt;&lt;span&gt; id &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;in&lt;&#x2F;span&gt;&lt;span&gt; actions.insertions.solid.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;drain&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;..&lt;&#x2F;span&gt;&lt;span&gt;) {
&lt;&#x2F;span&gt;&lt;span&gt;        state.solid.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;insert&lt;&#x2F;span&gt;&lt;span&gt;(id);
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; repeated for each flag component type
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Here are some example actions. Each is expressed as an “action constructor” function which populates
an empty action.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;move_character&lt;&#x2F;span&gt;&lt;span&gt;(character_id: EntityId, direction: Direction,
&lt;&#x2F;span&gt;&lt;span&gt;        state: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;EntityStore, action: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut&lt;&#x2F;span&gt;&lt;span&gt; Action) {
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; current_position &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; state.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;get_position&lt;&#x2F;span&gt;&lt;span&gt;(character_id)
&lt;&#x2F;span&gt;&lt;span&gt;        .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;expect&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#183691;&quot;&gt;&amp;quot;Attempt to move entity with no position&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;);
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; new_position &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; current_position &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;+&lt;&#x2F;span&gt;&lt;span&gt; direction.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;unit_vector&lt;&#x2F;span&gt;&lt;span&gt;();
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    action.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;insert_position&lt;&#x2F;span&gt;&lt;span&gt;(character_id, new_position);
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;open_door&lt;&#x2F;span&gt;&lt;span&gt;(door_id: EntityId, action: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut&lt;&#x2F;span&gt;&lt;span&gt; Action) {
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    action.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;remove_solid&lt;&#x2F;span&gt;&lt;span&gt;(door_id);
&lt;&#x2F;span&gt;&lt;span&gt;    action.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;insert_tile&lt;&#x2F;span&gt;&lt;span&gt;(door_id, TileType::OpenDoor);
&lt;&#x2F;span&gt;&lt;span&gt;    action.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;insert_door_state&lt;&#x2F;span&gt;&lt;span&gt;(door_id, DoorState::Open);
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;close_door&lt;&#x2F;span&gt;&lt;span&gt;(door_id: EntityId, action: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut&lt;&#x2F;span&gt;&lt;span&gt; Action) {
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    action.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;insert_solid&lt;&#x2F;span&gt;&lt;span&gt;(door_id);
&lt;&#x2F;span&gt;&lt;span&gt;    action.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;insert_tile&lt;&#x2F;span&gt;&lt;span&gt;(door_id, TileType::ClosedDoor);
&lt;&#x2F;span&gt;&lt;span&gt;    action.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;insert_door_state&lt;&#x2F;span&gt;&lt;span&gt;(door_id, DoorState::Closed);
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Note how none of the functions above modify the game’s state directly, but
rather construct an &lt;code&gt;Action&lt;&#x2F;code&gt; which describes how the state will be modified.&lt;&#x2F;p&gt;
&lt;p&gt;It will be convenient to be able to talk about the type of an action without
instantiating it:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;enum &lt;&#x2F;span&gt;&lt;span&gt;ActionType {
&lt;&#x2F;span&gt;&lt;span&gt;    MoveCharacter(EntityId, Direction),
&lt;&#x2F;span&gt;&lt;span&gt;    OpenDoor(EntityId),
&lt;&#x2F;span&gt;&lt;span&gt;    CloseDoor(EntityId),
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Given an &lt;code&gt;ActionType&lt;&#x2F;code&gt;, a &lt;code&gt;&amp;amp;EntityStore&lt;&#x2F;code&gt;, and a &lt;code&gt;&amp;amp;mut Action&lt;&#x2F;code&gt;, it’s possible to
call the appropriate action constructor with all its arguments:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;create_action&lt;&#x2F;span&gt;&lt;span&gt;(action_type: ActionType, state: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;EntityStore, action: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut&lt;&#x2F;span&gt;&lt;span&gt; Action) {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; `action` is assumed to be initially empty
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;match&lt;&#x2F;span&gt;&lt;span&gt; action_type {
&lt;&#x2F;span&gt;&lt;span&gt;        MoveCharacter(entity_id, direction) &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;move_character&lt;&#x2F;span&gt;&lt;span&gt;(entity_id, direction, state, action);
&lt;&#x2F;span&gt;&lt;span&gt;        }
&lt;&#x2F;span&gt;&lt;span&gt;        OpenDoor(entity_id) &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;open_door&lt;&#x2F;span&gt;&lt;span&gt;(entity_id, state, action);
&lt;&#x2F;span&gt;&lt;span&gt;        }
&lt;&#x2F;span&gt;&lt;span&gt;        CloseDoor(entity_id) &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;close_door&lt;&#x2F;span&gt;&lt;span&gt;(entity_id, state, action);
&lt;&#x2F;span&gt;&lt;span&gt;        }
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h2 id=&quot;game-logic-rules&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#game-logic-rules&quot; aria-label=&quot;Anchor link for: game-logic-rules&quot;&gt;Game Logic: Rules&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;A game can have many rules. Each rules contains some logic that examines the
current state of the game, and an action, and decides:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;whether the action is allowed to occur&lt;&#x2F;li&gt;
&lt;li&gt;which additional actions should occur&lt;&#x2F;li&gt;
&lt;li&gt;whether additional rules should be checked&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Here’s an example that encodes the mechanic where bumping into a
closed door will open the door.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;enum &lt;&#x2F;span&gt;&lt;span&gt;ActionStatus {
&lt;&#x2F;span&gt;&lt;span&gt;    Accept,
&lt;&#x2F;span&gt;&lt;span&gt;    Reject,
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;enum &lt;&#x2F;span&gt;&lt;span&gt;RuleStatus {
&lt;&#x2F;span&gt;&lt;span&gt;    KeepChecking,
&lt;&#x2F;span&gt;&lt;span&gt;    StopChecking,
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;bump_open_doors&lt;&#x2F;span&gt;&lt;span&gt;(action: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;Action, state: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;EntityStore,
&lt;&#x2F;span&gt;&lt;span&gt;                   reactions: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Vec&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;ActionType&amp;gt;)
&lt;&#x2F;span&gt;&lt;span&gt;                   -&amp;gt; (ActionStatus, RuleStatus) {
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; loop through all positions set by the action
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;for &lt;&#x2F;span&gt;&lt;span&gt;(id, position) &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;in&lt;&#x2F;span&gt;&lt;span&gt; action.insertions.position.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;iter&lt;&#x2F;span&gt;&lt;span&gt;() {
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; Only proceed if this entity can actually open doors
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;if !&lt;&#x2F;span&gt;&lt;span&gt;state.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;contains_can_open_doors&lt;&#x2F;span&gt;&lt;span&gt;(id) {
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;continue&lt;&#x2F;span&gt;&lt;span&gt;;
&lt;&#x2F;span&gt;&lt;span&gt;        }
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; I promise I&amp;#39;ll explain this below!
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;if let &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Some&lt;&#x2F;span&gt;&lt;span&gt;(door_id) &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;GET_DOOR_IN_CELL&lt;&#x2F;span&gt;&lt;span&gt;(position) {
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; if the entity would move into a cell with a door...
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; ...open the door...
&lt;&#x2F;span&gt;&lt;span&gt;            reactions.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;push&lt;&#x2F;span&gt;&lt;span&gt;(ActionType::OpenDoor(door_id));
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; ...and prevent the move from occuring.
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;return &lt;&#x2F;span&gt;&lt;span&gt;(ActionStatus::Reject, RuleStatus::StopChecking);
&lt;&#x2F;span&gt;&lt;span&gt;        }
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; no doors were bumped, so check other rules
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;return &lt;&#x2F;span&gt;&lt;span&gt;(ActionStatus::Accept, RuleStatus::KeepChecking);
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The first unusual thing one might notice is the fact that the rule loops over
all the entities that moved. Since actions can contain an arbitrary number of
changed components, this is required in case multiple entities move in an action.
Since an action can either be accepted or rejected, if multiple entities attempt
to move, and one of the moves is invalid, the action will still be rejected.
Having fine-grained actions (where each action represents a small change) allows
rules to be more powerful, without having to worry about “collateral damage”,
where some valid parts of an action don’t go ahead because of other invalid
parts of the same action.&lt;&#x2F;p&gt;
&lt;p&gt;The next thing to note is that the rule doesn’t just open the door there and
then. Instead, it queues up an action that will open the door. This will be an
action just like any other, and will go through the same rule-checking, so
there’s a possibility that the door won’t open, such as if the door is locked.&lt;&#x2F;p&gt;
&lt;p&gt;Now, what’s going on with that &lt;code&gt;GET_DOOR_IN_CELL&lt;&#x2F;code&gt; function. So far I haven’t
talked at all about reasoning about individual cells - only individual entities
or components. The &lt;code&gt;EntityStore&lt;&#x2F;code&gt; described earlier has no notion of cells, and
could be used for non grid-based games. All my applications of this engine so
far &lt;em&gt;have&lt;&#x2F;em&gt; been for games on a 2d grid, and most rules want to talk about
properties of cells, as well as properties of entities. To enable this, I use a
spatial hash, which I’ll introduce now, and elaborate more on rules later.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;spatial-hashing-interlude&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#spatial-hashing-interlude&quot; aria-label=&quot;Anchor link for: spatial-hashing-interlude&quot;&gt;Spatial Hashing Interlude&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;In games where the world is represented as a grid, it’s useful to be able to
reason about entire cells in the grid. At the very least, it would be nice to
easily iterate through a list of entities in a particular cell. I also want to,
have properties of cells based on aggregating over components of the entities
in the cell. For example, if a cell contains at least one entity which has the
&lt;strong&gt;solid&lt;&#x2F;strong&gt; component, I want that cell to be considered solid.&lt;&#x2F;p&gt;
&lt;p&gt;There’s nothing too exciting about implementing a 2d grid of cells. Suffice it
to say I have a type &lt;code&gt;SpatialHashTable&lt;&#x2F;code&gt; with the following interface:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;impl &lt;&#x2F;span&gt;&lt;span&gt;SpatialHashTable {
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; Update the spatial hash table with an action that&amp;#39;s about
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; to be applied. In order for the spatial hash table to
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; accurately reflect the state of its corresponding
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; EntityStore, this method must be called each time an
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; action is committed to said EntityStore.
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;update&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut &lt;&#x2F;span&gt;&lt;span&gt;self, action: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;Action) { &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;... &lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; Returns a particular cell in the spatial hash table which
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; can be queried further.
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;get&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;self, x: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;usize&lt;&#x2F;span&gt;&lt;span&gt;, y: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;usize&lt;&#x2F;span&gt;&lt;span&gt;) -&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;SpatialHashCell { &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;... &lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The cells are more interesting. Each cell maintains a set containing the ids of
all entities in the cell. When an entity moves, the
entity id set of the source cell and destination cell must be updated.
Additionally, for aggregate values, each time an entity moves or the component
relevant to the aggregate changes, the aggregate value must be updated.&lt;&#x2F;p&gt;
&lt;p&gt;There are different ways to aggregate properties of cells, with different use
cases. This post will cover two different aggregates:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Booleans that are true if there is at least one entity with a
certain component in a cell. The cell will maintain a count
of the number of entities with the component.&lt;&#x2F;li&gt;
&lt;li&gt;Sets that store the ids of all the entities in a cell with a given
component.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;struct &lt;&#x2F;span&gt;&lt;span&gt;SpatialHashCell {
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; all the entities in this cell
&lt;&#x2F;span&gt;&lt;span&gt;    entities: HashSet&amp;lt;EntityId&amp;gt;,
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; keep track of the number of solid entities in this cell
&lt;&#x2F;span&gt;&lt;span&gt;    solid: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;usize&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; maintain a set of entities with the `door_state` component
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; in this cell
&lt;&#x2F;span&gt;&lt;span&gt;    door_state: HashSet&amp;lt;EntityId&amp;gt;,
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;impl &lt;&#x2F;span&gt;&lt;span&gt;SpatialHashCell {
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; returns true iff there is at least one solid entity
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; in this cell
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;is_solid&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;self) -&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;bool &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;        self.solid &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;0
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; returns the id of an arbitrarily chosen entity
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; in this cell with the `door_state` component
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;any_door_state&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;self) -&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Option&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;EntityId&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;        self.door_state.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;iter&lt;&#x2F;span&gt;&lt;span&gt;().&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;next&lt;&#x2F;span&gt;&lt;span&gt;().&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;map&lt;&#x2F;span&gt;&lt;span&gt;(|s| &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;*&lt;&#x2F;span&gt;&lt;span&gt;s)
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;One may question the sense of allowing multiple entities with the &lt;strong&gt;door_state&lt;&#x2F;strong&gt;
component to exist in a single cell. There are unlikely to be any realistic
scenarios where there are multiple doors with the same position. However, the
simplest way to implement the entity store is allow it to store any combinations
of entities, and implement higher-level policy to be elsewhere (e.g. in actions
or rules).&lt;&#x2F;p&gt;
&lt;h2 id=&quot;back-to-rules&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#back-to-rules&quot; aria-label=&quot;Anchor link for: back-to-rules&quot;&gt;Back to Rules&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;Rules now take an additional argument: a &lt;code&gt;SpatialHashTable&lt;&#x2F;code&gt;!&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;bump_open_doors&lt;&#x2F;span&gt;&lt;span&gt;(action: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;Action, state: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;EntityStore,
&lt;&#x2F;span&gt;&lt;span&gt;                   spatial_hash: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;SpatialHashTable, &lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; &amp;lt;-- NEW!
&lt;&#x2F;span&gt;&lt;span&gt;                   reactions: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Vec&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;ActionType&amp;gt;)
&lt;&#x2F;span&gt;&lt;span&gt;                   -&amp;gt; (ActionStatus, RuleStatus) {
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; loop through all positions set by the action
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;for &lt;&#x2F;span&gt;&lt;span&gt;(id, position) &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;in&lt;&#x2F;span&gt;&lt;span&gt; action.insertions.position.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;iter&lt;&#x2F;span&gt;&lt;span&gt;() {
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; Only proceed if this entity can actually open doors
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;if !&lt;&#x2F;span&gt;&lt;span&gt;state.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;contains_can_open_doors&lt;&#x2F;span&gt;&lt;span&gt;(id) {
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;continue&lt;&#x2F;span&gt;&lt;span&gt;;
&lt;&#x2F;span&gt;&lt;span&gt;        }
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; NEW!
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;if let &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Some&lt;&#x2F;span&gt;&lt;span&gt;(door_id) &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=
&lt;&#x2F;span&gt;&lt;span&gt;            spatial_hash.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;get&lt;&#x2F;span&gt;&lt;span&gt;(position).&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;any_door_state&lt;&#x2F;span&gt;&lt;span&gt;() {
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; if the entity would move into a cell with a door...
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; ...open the door...
&lt;&#x2F;span&gt;&lt;span&gt;            reactions.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;push&lt;&#x2F;span&gt;&lt;span&gt;(ActionType::OpenDoor(door_id));
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; ...and prevent the move from occuring.
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;return &lt;&#x2F;span&gt;&lt;span&gt;(ActionStatus::Reject, RuleStatus::StopChecking);
&lt;&#x2F;span&gt;&lt;span&gt;        }
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; no doors were bumped, so check other rules
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;return &lt;&#x2F;span&gt;&lt;span&gt;(ActionStatus::Accept, RuleStatus::KeepChecking);
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;How should we handle an action that moves an entity, and gives it the ability to
open doors at the same time? Suppose a character that could not open doors
gained the ability to open doors, and moved into a cell containing a door, as a
single action. I’d like the door to open in response.&lt;&#x2F;p&gt;
&lt;p&gt;Note that the check &lt;code&gt;if !state.contains_can_open_doors(id) {&lt;&#x2F;code&gt;
queries the current state of the game only. Since the character currently
can’t open doors, this check will prevent the door from being opened.&lt;&#x2F;p&gt;
&lt;p&gt;I could add an additional check that examines the action, to see if the entity
moving into a door is about to gain the ability to open doors, but this feels
cumbersome. Instead, I want a way to talk about the state of the game after an
action has been committed, without actually committing the action.&lt;&#x2F;p&gt;
&lt;p&gt;Since the game state and actions are both described in terms of components, I
can turn a reference to a game state and a reference to an action into something
that looks like the state of the game following the action:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;struct &lt;&#x2F;span&gt;&lt;span&gt;EntityStoreAfterAction&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;#39;a&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;    entity_store: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&amp;#39;a&lt;&#x2F;span&gt;&lt;span&gt; EntityStore,
&lt;&#x2F;span&gt;&lt;span&gt;    action: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&amp;#39;a&lt;&#x2F;span&gt;&lt;span&gt; Action,
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; the same getters as an EntityStore
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;impl&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;#39;a&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt; EntityStoreAfterAction&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;#39;a&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;get_position&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;self, id: EntityId) -&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Option&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;(&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;isize&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;isize&lt;&#x2F;span&gt;&lt;span&gt;)&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; if the component is being inserted, return it
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;if let &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Some&lt;&#x2F;span&gt;&lt;span&gt;(value) &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span&gt;self.action.insertions.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;get_position&lt;&#x2F;span&gt;&lt;span&gt;(id) {
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;return &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Some&lt;&#x2F;span&gt;&lt;span&gt;(value);
&lt;&#x2F;span&gt;&lt;span&gt;        }
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; if the component is being removed, prevent the original
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; value from being returned
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;if &lt;&#x2F;span&gt;&lt;span&gt;self.action.removals.position.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;contains&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;id) {
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;return &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;None&lt;&#x2F;span&gt;&lt;span&gt;;
&lt;&#x2F;span&gt;&lt;span&gt;        }
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; return the original value
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;return &lt;&#x2F;span&gt;&lt;span&gt;self.entity_store.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;get_position&lt;&#x2F;span&gt;&lt;span&gt;(id);
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;An &lt;code&gt;EntityStoreAfterAction&lt;&#x2F;code&gt; looks like an &lt;code&gt;EntityStore&lt;&#x2F;code&gt;! They both implement the
same query interface, but &lt;code&gt;EntityStoreAfterAction&lt;&#x2F;code&gt; lets us query the future.&lt;&#x2F;p&gt;
&lt;p&gt;Modifying the rule to use &lt;code&gt;EntityStoreAfterAction&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;bump_open_doors&lt;&#x2F;span&gt;&lt;span&gt;(action: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;Action, state: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;EntityStore,
&lt;&#x2F;span&gt;&lt;span&gt;                   spatial_hash: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;SpatialHashTable,
&lt;&#x2F;span&gt;&lt;span&gt;                   reactions: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Vec&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;ActionType&amp;gt;)
&lt;&#x2F;span&gt;&lt;span&gt;                   -&amp;gt; (ActionStatus, RuleStatus) {
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; NEW!
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; future_state &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; EntityStoreAfterAction {
&lt;&#x2F;span&gt;&lt;span&gt;        entity_store: state,
&lt;&#x2F;span&gt;&lt;span&gt;        action: action,
&lt;&#x2F;span&gt;&lt;span&gt;    };
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; loop through all positions set by the action
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;for &lt;&#x2F;span&gt;&lt;span&gt;(id, position) &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;in&lt;&#x2F;span&gt;&lt;span&gt; action.insertions.position.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;iter&lt;&#x2F;span&gt;&lt;span&gt;() {
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; Only proceed if this entity can actually open doors
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;if !&lt;&#x2F;span&gt;&lt;span&gt;future_state.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;contains_can_open_doors&lt;&#x2F;span&gt;&lt;span&gt;(id) { &lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; &amp;lt;-- CHANGED!
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;continue&lt;&#x2F;span&gt;&lt;span&gt;;
&lt;&#x2F;span&gt;&lt;span&gt;        }
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;if let &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Some&lt;&#x2F;span&gt;&lt;span&gt;(door_id) &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=
&lt;&#x2F;span&gt;&lt;span&gt;            spatial_hash.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;get&lt;&#x2F;span&gt;&lt;span&gt;(position).&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;any_door_state&lt;&#x2F;span&gt;&lt;span&gt;() {
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; if the entity would move into a cell with a door...
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; ...open the door...
&lt;&#x2F;span&gt;&lt;span&gt;            reactions.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;push&lt;&#x2F;span&gt;&lt;span&gt;(ActionType::OpenDoor(door_id));
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; ...and prevent the move from occuring.
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;return &lt;&#x2F;span&gt;&lt;span&gt;(ActionStatus::Reject, RuleStatus::StopChecking);
&lt;&#x2F;span&gt;&lt;span&gt;        }
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; no doors were bumped, so check other rules
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;return &lt;&#x2F;span&gt;&lt;span&gt;(ActionStatus::Accept, RuleStatus::KeepChecking);
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The order in which rules are checked effects their outcome. For example,
consider the following collision rule, that states that solid entities cannot
move through other solid entities.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;collision&lt;&#x2F;span&gt;&lt;span&gt;(action: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;Action, state: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;EntityStore,
&lt;&#x2F;span&gt;&lt;span&gt;             spatial_hash: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;SpatialHashTable,
&lt;&#x2F;span&gt;&lt;span&gt;             reactions: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Vec&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;ActionType&amp;gt;)
&lt;&#x2F;span&gt;&lt;span&gt;             -&amp;gt; (ActionStatus, RuleStatus) {
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; future_state &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; EntityStoreAfterAction {
&lt;&#x2F;span&gt;&lt;span&gt;        entity_store: state,
&lt;&#x2F;span&gt;&lt;span&gt;        action: action,
&lt;&#x2F;span&gt;&lt;span&gt;    };
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;for &lt;&#x2F;span&gt;&lt;span&gt;(id, position) &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;in&lt;&#x2F;span&gt;&lt;span&gt; action.insertions.position.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;iter&lt;&#x2F;span&gt;&lt;span&gt;() {
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;if !&lt;&#x2F;span&gt;&lt;span&gt;future_state.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;contains_solid&lt;&#x2F;span&gt;&lt;span&gt;(id) {
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;continue&lt;&#x2F;span&gt;&lt;span&gt;;
&lt;&#x2F;span&gt;&lt;span&gt;        }
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;if&lt;&#x2F;span&gt;&lt;span&gt; spatial_hash.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;get&lt;&#x2F;span&gt;&lt;span&gt;(position).&lt;&#x2F;span&gt;&lt;span style=&quot;color:#62a35c;&quot;&gt;is_solid&lt;&#x2F;span&gt;&lt;span&gt;() {
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;return &lt;&#x2F;span&gt;&lt;span&gt;(ActionStatus::Reject, RuleStatus::StopChecking);
&lt;&#x2F;span&gt;&lt;span&gt;        }
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;return &lt;&#x2F;span&gt;&lt;span&gt;(ActionStatus::Accept, RuleStatus::KeepChecking);
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Since closed doors are solid, if the &lt;code&gt;collision&lt;&#x2F;code&gt; rule was checked before the
&lt;code&gt;bump_open_doors&lt;&#x2F;code&gt; rule, the action would be rejected and we’d stop checking
rules, so the logic that opens doors would never run. Thus, &lt;code&gt;bump_open_doors&lt;&#x2F;code&gt;
should be checked before &lt;code&gt;collision&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;putting-it-all-together&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#putting-it-all-together&quot; aria-label=&quot;Anchor link for: putting-it-all-together&quot;&gt;Putting it all together&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;This is roughly how my game loop works:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;&#x2F;&#x2F; the type of a rule function (e.g. collision)
&lt;&#x2F;span&gt;&lt;span&gt;type RuleFn = ...;
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;&#x2F;&#x2F; knows which entity&amp;#39;s turn it is
&lt;&#x2F;span&gt;&lt;span&gt;struct TurnSchedule { ... };
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;struct Game {
&lt;&#x2F;span&gt;&lt;span&gt;    &#x2F;&#x2F; All entities and components in the game world.
&lt;&#x2F;span&gt;&lt;span&gt;    state: EntityStore,
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    &#x2F;&#x2F; List of rules in the order they will be checked.
&lt;&#x2F;span&gt;&lt;span&gt;    rules: Vec&amp;lt;RuleFn&amp;gt;,
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    &#x2F;&#x2F; Used to determine whose turn it is.
&lt;&#x2F;span&gt;&lt;span&gt;    schedule: TurnSchedule,
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    &#x2F;&#x2F; It turns out you only need to have a single action
&lt;&#x2F;span&gt;&lt;span&gt;    &#x2F;&#x2F; instantiated at a time. Store this as part of the
&lt;&#x2F;span&gt;&lt;span&gt;    &#x2F;&#x2F; game to remove the overhead of creating a new
&lt;&#x2F;span&gt;&lt;span&gt;    &#x2F;&#x2F; action each time we need one.
&lt;&#x2F;span&gt;&lt;span&gt;    action: Action,
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    &#x2F;&#x2F; A queue of actions waiting to be processed in the
&lt;&#x2F;span&gt;&lt;span&gt;    &#x2F;&#x2F; current turn.
&lt;&#x2F;span&gt;&lt;span&gt;    pending_actions: VecDeque&amp;lt;ActionType&amp;gt;,
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    &#x2F;&#x2F; Rules have the ability to enqueue follow-on actions,
&lt;&#x2F;span&gt;&lt;span&gt;    &#x2F;&#x2F; which will also be processed by rules. The follow-on
&lt;&#x2F;span&gt;&lt;span&gt;    &#x2F;&#x2F; actions enqueued by a rule as it checks an action
&lt;&#x2F;span&gt;&lt;span&gt;    &#x2F;&#x2F; are only added to pending_actions if the action being
&lt;&#x2F;span&gt;&lt;span&gt;    &#x2F;&#x2F; checked gets accepted. Follow-on actions are
&lt;&#x2F;span&gt;&lt;span&gt;    &#x2F;&#x2F; temporarily stored here, and added to pending_actions
&lt;&#x2F;span&gt;&lt;span&gt;    &#x2F;&#x2F; if the current action is accepted.
&lt;&#x2F;span&gt;&lt;span&gt;    &#x2F;&#x2F;
&lt;&#x2F;span&gt;&lt;span&gt;    &#x2F;&#x2F; There is a separate queue for actions enqueued by
&lt;&#x2F;span&gt;&lt;span&gt;    &#x2F;&#x2F; accepting rules and rejecting rules. This allows
&lt;&#x2F;span&gt;&lt;span&gt;    &#x2F;&#x2F; accepting rules to enqueue actions that will only
&lt;&#x2F;span&gt;&lt;span&gt;    &#x2F;&#x2F; occur if the action ends up getting accepted.
&lt;&#x2F;span&gt;&lt;span&gt;    follon_on_accepted: VecDequeue&amp;lt;ActionType&amp;gt;,
&lt;&#x2F;span&gt;&lt;span&gt;    follon_on_rejected: VecDequeue&amp;lt;ActionType&amp;gt;,
&lt;&#x2F;span&gt;&lt;span&gt;    follon_on_current: VecDequeue&amp;lt;ActionType&amp;gt;,
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;impl Game {
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    fn game_loop(&amp;amp;mut self) {
&lt;&#x2F;span&gt;&lt;span&gt;        loop {
&lt;&#x2F;span&gt;&lt;span&gt;            &#x2F;&#x2F; Figure out whose turn it is.
&lt;&#x2F;span&gt;&lt;span&gt;            let entity_id: EntityId =
&lt;&#x2F;span&gt;&lt;span&gt;                self.schedule.next_turn();
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;            &#x2F;&#x2F; The current entity decides an action.
&lt;&#x2F;span&gt;&lt;span&gt;            &#x2F;&#x2F; This waits for player input if it&amp;#39;s
&lt;&#x2F;span&gt;&lt;span&gt;            &#x2F;&#x2F; the player&amp;#39;s turn, and invokes the AI
&lt;&#x2F;span&gt;&lt;span&gt;            &#x2F;&#x2F; if it&amp;#39;s an NPC&amp;#39;s turn.
&lt;&#x2F;span&gt;&lt;span&gt;            &#x2F;&#x2F; The details of choosing an action are
&lt;&#x2F;span&gt;&lt;span&gt;            &#x2F;&#x2F; out of scope.
&lt;&#x2F;span&gt;&lt;span&gt;            let action_type: ActionType =
&lt;&#x2F;span&gt;&lt;span&gt;                CHOOSE_ACTION(&amp;amp;self.state, entity_id);
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;            &#x2F;&#x2F; Equeue the action for processing
&lt;&#x2F;span&gt;&lt;span&gt;            self.pending_actions.push_back(action_type);
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;            &#x2F;&#x2F; Check rules, and handle any follow-on
&lt;&#x2F;span&gt;&lt;span&gt;            &#x2F;&#x2F; actions.
&lt;&#x2F;span&gt;&lt;span&gt;            self.process_actions();
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;            &#x2F;&#x2F; Allow the entity to take another turn
&lt;&#x2F;span&gt;&lt;span&gt;            &#x2F;&#x2F; at some point in the future.
&lt;&#x2F;span&gt;&lt;span&gt;            self.schedule.insert(entity_id);
&lt;&#x2F;span&gt;&lt;span&gt;        }
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    fn process_actions(&amp;amp;mut self) {
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;        &#x2F;&#x2F; Repeat until there are no pending actions.
&lt;&#x2F;span&gt;&lt;span&gt;        while let Some(action_type) =
&lt;&#x2F;span&gt;&lt;span&gt;            self.pending_actions.pop_front() {
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;            &#x2F;&#x2F; Populate self.action based on the
&lt;&#x2F;span&gt;&lt;span&gt;            &#x2F;&#x2F; value of action_type.
&lt;&#x2F;span&gt;&lt;span&gt;            self.action.instantiate_from(action_type,
&lt;&#x2F;span&gt;&lt;span&gt;                                         &amp;amp;self.state);
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;            let mut accepted = true;
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;            &#x2F;&#x2F; For each rule
&lt;&#x2F;span&gt;&lt;span&gt;            for rule in self.rules.iter() {
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;                &#x2F;&#x2F; Check the rule
&lt;&#x2F;span&gt;&lt;span&gt;                let (action_status, rule_status) =
&lt;&#x2F;span&gt;&lt;span&gt;                    rule(&amp;amp;self.action. &amp;amp;self.state,
&lt;&#x2F;span&gt;&lt;span&gt;                         &amp;amp;mut self.follow_on_current);
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;                &#x2F;&#x2F; If a single rule rejects an action,
&lt;&#x2F;span&gt;&lt;span&gt;                &#x2F;&#x2F; the action is rejected.
&lt;&#x2F;span&gt;&lt;span&gt;                if action_status == ActionStatus::Reject {
&lt;&#x2F;span&gt;&lt;span&gt;                    accepted = false;
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;                    &#x2F;&#x2F; Drain follow-on actions into
&lt;&#x2F;span&gt;&lt;span&gt;                    &#x2F;&#x2F; rejected queue.
&lt;&#x2F;span&gt;&lt;span&gt;                    for a in self.follow_on_current.drain(..) {
&lt;&#x2F;span&gt;&lt;span&gt;                        self.follow_on_rejected.push_back(a);
&lt;&#x2F;span&gt;&lt;span&gt;                    }
&lt;&#x2F;span&gt;&lt;span&gt;                } else {
&lt;&#x2F;span&gt;&lt;span&gt;                    &#x2F;&#x2F; Drain follow-on actions into
&lt;&#x2F;span&gt;&lt;span&gt;                    &#x2F;&#x2F; accepted queue.
&lt;&#x2F;span&gt;&lt;span&gt;                    for a in self.follow_on_current.drain(..) {
&lt;&#x2F;span&gt;&lt;span&gt;                        self.follow_on_accepted.push_back(a);
&lt;&#x2F;span&gt;&lt;span&gt;                    }
&lt;&#x2F;span&gt;&lt;span&gt;                }
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;                &#x2F;&#x2F; Stop checking rules if the rule say so.
&lt;&#x2F;span&gt;&lt;span&gt;                if rule_status == RuleStatus::StopChecking {
&lt;&#x2F;span&gt;&lt;span&gt;                    break;
&lt;&#x2F;span&gt;&lt;span&gt;                }
&lt;&#x2F;span&gt;&lt;span&gt;            }
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;            if accepted {
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;                &#x2F;&#x2F; Apply the action, clearing the action in the
&lt;&#x2F;span&gt;&lt;span&gt;                &#x2F;&#x2F; process.
&lt;&#x2F;span&gt;&lt;span&gt;                commit_action(&amp;amp;mut self.state, &amp;amp;mut self.action);
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;                &#x2F;&#x2F; It&amp;#39;s only necessary to re-draw the scene after
&lt;&#x2F;span&gt;&lt;span&gt;                &#x2F;&#x2F; something has changed.
&lt;&#x2F;span&gt;&lt;span&gt;                &#x2F;&#x2F; The details of rendering are out of scope.
&lt;&#x2F;span&gt;&lt;span&gt;                RENDER();
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;                &#x2F;&#x2F; Enqueue all the follow-on actions.
&lt;&#x2F;span&gt;&lt;span&gt;                for a in self.follow_on_accepted.drain(..) {
&lt;&#x2F;span&gt;&lt;span&gt;                    self.pending_actions.push_back(a);
&lt;&#x2F;span&gt;&lt;span&gt;                }
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;            } else {
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;                &#x2F;&#x2F; The action was rejected.
&lt;&#x2F;span&gt;&lt;span&gt;                &#x2F;&#x2F; Clear the action.
&lt;&#x2F;span&gt;&lt;span&gt;                self.action.clear();
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;                &#x2F;&#x2F; Enqueue all the follow-on actions.
&lt;&#x2F;span&gt;&lt;span&gt;                for a in self.follow_on_rejected.drain(..) {
&lt;&#x2F;span&gt;&lt;span&gt;                    self.pending_actions.push_back(a);
&lt;&#x2F;span&gt;&lt;span&gt;                }
&lt;&#x2F;span&gt;&lt;span&gt;            }
&lt;&#x2F;span&gt;&lt;span&gt;        }
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h2 id=&quot;limitations&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#limitations&quot; aria-label=&quot;Anchor link for: limitations&quot;&gt;Limitations&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;While using this engine for the 7DRL, I noticed some problems with its current
design.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;isolated-rules&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#isolated-rules&quot; aria-label=&quot;Anchor link for: isolated-rules&quot;&gt;Isolated Rules&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;Splitting up the game logic into many individual rules leads to high cognitive load.
The motivation for having lots of small, modular, isolated rules, was to
&lt;em&gt;decrease&lt;&#x2F;em&gt; cognitive load, but it ended up having the opposite effect. The problem
is that rules aren’t completely isolated. If rules are checked in the wrong order
they can be unintentionally skipped. Rules have the ability to make the global decision
of whether or not to keep checking the remaining rules.&lt;&#x2F;p&gt;
&lt;p&gt;It’s not even clear whether attempting to
isolate rules from one another is the right approach. A lot of the fun in turn-based
games comes from the interaction of different mechanics, so forcing the rules to
be isolated may be harmful, compared to a framework that allows rules to
explicitly cooperate.&lt;&#x2F;p&gt;
&lt;p&gt;Most of the rules reason about changes in position. This means, each rule must
loop over all the changes in position in the current action, and apply some
policy. The isolation between rules leads to several rules checking the same
component, and unnecessarily repeating work. This is more evidencing suggesting
I should stop isolating rules from one another.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;intra-turn-real-time-animation&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#intra-turn-real-time-animation&quot; aria-label=&quot;Anchor link for: intra-turn-real-time-animation&quot;&gt;Intra-turn Real-time Animation&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;My engine allows a delay to be added between actions during a turn, to allow
real-time animations to be implemented as part of a turn’s resolution. In the gif
below, the bullet
leaving the van and hitting the barrel, and the subsequent explosions, are all
part of a single turn.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;modifying-entity-component-system-for-turn-based-games&#x2F;explosion.gif&quot; alt=&quot;explosion.gif&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;This is implemented using rules. Entities can have a &lt;strong&gt;velocity&lt;&#x2F;strong&gt; component, and
there is a rule that detects when an entity moves because of their velocity, and
schedules an additional action to move them again, resulting in a chain of
repeated move actions being committed.&lt;&#x2F;p&gt;
&lt;p&gt;This is a testament to the expressive power of actions and rules, but it feels
unnecessarily complicated. Also, when something goes wrong, debugging this chain
of actions and rules is a nightmare.&lt;&#x2F;p&gt;
&lt;p&gt;To simplify this, I’m thinking about adding a hook to the turn-resolution loop
that is called at each discrete point in (real) time as a turn is resolved. It would allow some game logic to
be invoked periodically to implement real-time mechanics. For example, it would take
all the entities with a velocity, and update their position such that they move
under their velocity. Sound familiar? I guess there’s a place for &lt;strong&gt;systems&lt;&#x2F;strong&gt; in
my engine after all.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;summary&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#summary&quot; aria-label=&quot;Anchor link for: summary&quot;&gt;Summary&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;My turn-based game engine uses a modified form of ECS. I still store data using entities
and components, but I found the idea of systems to be more suited to real-time
games. In my engine, I replace &lt;strong&gt;systems&lt;&#x2F;strong&gt; with &lt;strong&gt;actions&lt;&#x2F;strong&gt; and &lt;strong&gt;rules&lt;&#x2F;strong&gt;. Actions
describe discrete changes to the game’s state, in terms of entities and
components. Rules determine whether an action is allowed to happen, and which
additional actions will happen as a result.&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Programming Languages Make Terrible Game Engines</title>
          <pubDate>Sat, 25 Mar 2017 14:26:01 +1000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/programming-languages-make-terrible-game-engines/</link>
          <guid>https://www.gridbugs.org/programming-languages-make-terrible-game-engines/</guid>
          <description xml:base="https://www.gridbugs.org/programming-languages-make-terrible-game-engines/">&lt;p&gt;This is the first of a series of posts I’m writing to explain the
inner workings of the game engine I used for my 7DRL: &lt;a href=&quot;https:&#x2F;&#x2F;gridbugs.itch.io&#x2F;apocalypse-post&quot;&gt;Apocalypse
Post&lt;&#x2F;a&gt;. It motivates one of the
problems I set out to solve with the engine - how to represent the types of game
entities.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;edit&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#edit&quot; aria-label=&quot;Anchor link for: edit&quot;&gt;Edit&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;In the &lt;a href=&quot;https:&#x2F;&#x2F;www.reddit.com&#x2F;r&#x2F;roguelikedev&#x2F;comments&#x2F;61elh1&#x2F;programming_languages_make_terrible_game_engines&#x2F;&quot;&gt;discussion&lt;&#x2F;a&gt;
about this post, it was pointed out that the object-oriented examples below are
examples of bad object-oriented design. I agree with this, and I’m not trying to argue that
it’s impossible to design a good game engine using object-oriented programming.
The examples illustrate how someone new to building game engines might attempt
to use class inheritance to describe the types of game entities. The article
demonstrates the problems with this approach, and suggests a non-object-oriented
alternative.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;you-want-types&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#you-want-types&quot; aria-label=&quot;Anchor link for: you-want-types&quot;&gt;You want types&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;You’re making a game engine, and you want a way to categorize entities in your
game, so the engine knows what operations it can perform on an entity.
You want a way to express the fact that &lt;strong&gt;Weapons&lt;&#x2F;strong&gt; can be fired, &lt;strong&gt;Characters&lt;&#x2F;strong&gt; can act,
&lt;strong&gt;Equipment&lt;&#x2F;strong&gt; can be equipped, and so on.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;your-programming-language-has-types&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#your-programming-language-has-types&quot; aria-label=&quot;Anchor link for: your-programming-language-has-types&quot;&gt;Your programming language &lt;em&gt;has&lt;&#x2F;em&gt; types!&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;Preface: Don’t do this!&lt;&#x2F;p&gt;
&lt;p&gt;&lt;em&gt;Aha&lt;&#x2F;em&gt;, you say, &lt;em&gt;I just need to create abstract classes for &lt;strong&gt;Weapon&lt;&#x2F;strong&gt;, &lt;strong&gt;Character&lt;&#x2F;strong&gt;,
&lt;strong&gt;Equipment&lt;&#x2F;strong&gt;, and inherit them for concrete classes representing game entities&lt;&#x2F;em&gt;.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;java&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-java &quot;&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;class &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;GameEntity &lt;&#x2F;span&gt;&lt;span&gt;{ ... }
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;class &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Weapon &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;extends &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;GameEntity &lt;&#x2F;span&gt;&lt;span&gt;{ &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;int &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;damage&lt;&#x2F;span&gt;&lt;span&gt;(); }
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;class &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Character &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;extends &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;GameEntity &lt;&#x2F;span&gt;&lt;span&gt;{ &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Action &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;act&lt;&#x2F;span&gt;&lt;span&gt;(); }
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;class &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Equipment &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;extends &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;GameEntity &lt;&#x2F;span&gt;&lt;span&gt;{ &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;void &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;equip&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Character&lt;&#x2F;span&gt;&lt;span&gt;); }
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;class &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Sword &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;extends &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Weapon &lt;&#x2F;span&gt;&lt;span&gt;{ ... }
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;class &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Human &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;extends &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Character &lt;&#x2F;span&gt;&lt;span&gt;{ ... }
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;class &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Zombie &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;extends &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Character &lt;&#x2F;span&gt;&lt;span&gt;{ ... }
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;But hang on, swords can also be equipped, we need multiple inheritance:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;java&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-java &quot;&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;class &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;GameEntity &lt;&#x2F;span&gt;&lt;span&gt;{ ... }
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;interface &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Weapon &lt;&#x2F;span&gt;&lt;span&gt;{ &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;int &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;damage&lt;&#x2F;span&gt;&lt;span&gt;(); }
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;interface &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Character &lt;&#x2F;span&gt;&lt;span&gt;{ &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Action &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;act&lt;&#x2F;span&gt;&lt;span&gt;(); }
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;interface &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Equipment &lt;&#x2F;span&gt;&lt;span&gt;{ &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;void &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;equip&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Character&lt;&#x2F;span&gt;&lt;span&gt;); }
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;class &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Sword &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;extends &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;GameEntity &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;implements &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Weapon&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Equipable &lt;&#x2F;span&gt;&lt;span&gt;{ ... }
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;class &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Human &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;extends &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;GameEntity &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;implements &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Character &lt;&#x2F;span&gt;&lt;span&gt;{ ... }
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;class &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Zombie &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;extends &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;GameEntity &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;implements &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Character &lt;&#x2F;span&gt;&lt;span&gt;{ ... }
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;When a human is bitten by a zombie, they should turn into a zombie. Wait &lt;strong&gt;turn
into&lt;&#x2F;strong&gt;?&lt;&#x2F;p&gt;
&lt;p&gt;Perhaps we can do something like:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;java&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-java &quot;&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;interface &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;TurnsIntoZombie &lt;&#x2F;span&gt;&lt;span&gt;{ &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Zombie &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;turn_into&lt;&#x2F;span&gt;&lt;span&gt;(); }
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;class &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Human &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;extends &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;GameEntity
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;implements &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Character&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;TurnsIntoZombie &lt;&#x2F;span&gt;&lt;span&gt;{ ... }
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This raises some questions:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;What does &lt;code&gt;turn_into&lt;&#x2F;code&gt; actually do? It needs to take a bunch the fields from
the &lt;strong&gt;Human&lt;&#x2F;strong&gt;, (equipment, physical stats), and create a new &lt;strong&gt;Zombie&lt;&#x2F;strong&gt; with
those fields, since it would be nice if the re-animated human in some ways
resembled their former self. Somehow we also need to make the original
&lt;strong&gt;Human&lt;&#x2F;strong&gt; object unusable, and ensure that there are no references to it that
might still try to treat the copied fields as if they were part of a human.&lt;&#x2F;li&gt;
&lt;li&gt;There will probably be other types of character besides humans and zombies,
and some may implement &lt;strong&gt;TurnsIntoZombie&lt;&#x2F;strong&gt;.
This implies that whenever &lt;em&gt;something&lt;&#x2F;em&gt; is bitten by a zombie, we need to
check (at runtime) whether it turns into a zombie. Alternatively,
&lt;code&gt;turn_into&lt;&#x2F;code&gt; could be moved into the &lt;strong&gt;Character&lt;&#x2F;strong&gt; interface, and do nothing for
characters that shouldn’t become zombies (should it return &lt;code&gt;null&lt;&#x2F;code&gt;?).&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;How should game entities be stored? One can imagine using a collection of
&lt;strong&gt;GameEntity&lt;&#x2F;strong&gt;.
The major problem is that the type information is lost from the
entities in the array, and upon pulling something out of the array, we need to
check what it is, and then cast it appropriately. An alternative may be to use a
separate collection of entities for each entity type. Entities may belong to
multiple types (e.g. a &lt;strong&gt;Sword&lt;&#x2F;strong&gt; is both &lt;strong&gt;Equipable&lt;&#x2F;strong&gt; and a &lt;strong&gt;Weapon&lt;&#x2F;strong&gt;), so
each may appear in several collections, and we would then have to manage the fact
that if an item is destroyed, it must be removed from all the lists that contain
it.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;programming-language-types-map-poorly-to-game-entities&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#programming-language-types-map-poorly-to-game-entities&quot; aria-label=&quot;Anchor link for: programming-language-types-map-poorly-to-game-entities&quot;&gt;Programming language types map poorly to game entities&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;The cracks are starting to show:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Programming language types are static, in the sense that an object’s type
does not change. In games you want the types of game entities to be mutable.&lt;&#x2F;li&gt;
&lt;li&gt;You’re forced to use multiple inheritance if you want entities to fit into
multiple categories. Not all languages support this, and it comes with
its own set of problems.&lt;&#x2F;li&gt;
&lt;li&gt;You’re forced to check types at runtime. There’s nothing wrong with checking
types at runtime, but if you’re going to do it, why tie yourself to your
programming language’s type system?&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;composition-over-inheritance&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#composition-over-inheritance&quot; aria-label=&quot;Anchor link for: composition-over-inheritance&quot;&gt;Composition over Inheritance!&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;The big problem with mapping language types onto game entities is that language
types are often concerned with describing what an object &lt;strong&gt;is&lt;&#x2F;strong&gt;, whereas game
entities are best described in terms of what an object &lt;strong&gt;has&lt;&#x2F;strong&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Here’s how one might describe the example above, without trying to fit game
entities into language types. I’m switching from java to rust because I no
longer need to give examples of object-oriented programming (phew!).&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;struct &lt;&#x2F;span&gt;&lt;span&gt;GameEntity {
&lt;&#x2F;span&gt;&lt;span&gt;    weapon_damage: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Option&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;u64&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;,
&lt;&#x2F;span&gt;&lt;span&gt;    actor_state: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Option&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;ActorState&amp;gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; defined below
&lt;&#x2F;span&gt;&lt;span&gt;    human: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;bool&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;    zombie: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;bool&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; collection of keys into entity_table (below)
&lt;&#x2F;span&gt;&lt;span&gt;    equipment: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Option&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;HashSet&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;u64&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&amp;gt;,
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;struct &lt;&#x2F;span&gt;&lt;span&gt;GameState {
&lt;&#x2F;span&gt;&lt;span&gt;    entity_table: HashMap&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;u64&lt;&#x2F;span&gt;&lt;span&gt;, GameEntity&amp;gt;,
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;struct &lt;&#x2F;span&gt;&lt;span&gt;ActorState { ... }
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;impl &lt;&#x2F;span&gt;&lt;span&gt;ActorState {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;new_human_state&lt;&#x2F;span&gt;&lt;span&gt;() -&amp;gt; ActorState { &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;... &lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;new_zombie_state&lt;&#x2F;span&gt;&lt;span&gt;() -&amp;gt; ActorState { &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;... &lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut &lt;&#x2F;span&gt;&lt;span&gt;self) -&amp;gt; Action { &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;... &lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;new_sword&lt;&#x2F;span&gt;&lt;span&gt;(damage: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;u64&lt;&#x2F;span&gt;&lt;span&gt;) -&amp;gt; GameEntity {
&lt;&#x2F;span&gt;&lt;span&gt;    GameEntity {
&lt;&#x2F;span&gt;&lt;span&gt;        weapon_damage: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Some&lt;&#x2F;span&gt;&lt;span&gt;(damage),
&lt;&#x2F;span&gt;&lt;span&gt;        actor_state: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;None&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; a sword cannot act
&lt;&#x2F;span&gt;&lt;span&gt;        human: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;false&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;        zombie: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;false&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;        equipment: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;None&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;new_human&lt;&#x2F;span&gt;&lt;span&gt;() -&amp;gt; GameEntity {
&lt;&#x2F;span&gt;&lt;span&gt;    GameEntity {
&lt;&#x2F;span&gt;&lt;span&gt;        weapon_damage: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;None&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;        actor_state: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Some&lt;&#x2F;span&gt;&lt;span&gt;(ActorState::new_human_state()),
&lt;&#x2F;span&gt;&lt;span&gt;        human: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;true&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;        zombie: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;false&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;        equipment: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Some&lt;&#x2F;span&gt;&lt;span&gt;(HashSet::new()),
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Every entity in the game is a &lt;code&gt;GameEntity&lt;&#x2F;code&gt;. A &lt;code&gt;GameEntity&lt;&#x2F;code&gt; is a collection of
properties that an entity might have, and the categorization of the entity is
based on which properties have values, and what those values are. Each field is
either an &lt;code&gt;Option&lt;&#x2F;code&gt; which may contain some data, or a &lt;code&gt;bool&lt;&#x2F;code&gt; denoting the
existence of a property with no associated data.&lt;&#x2F;p&gt;
&lt;p&gt;Note that there are more efficient ways to represent entities than structs of
&lt;code&gt;Options&lt;&#x2F;code&gt; and &lt;code&gt;bools&lt;&#x2F;code&gt;. I’ll cover this in a later article.&lt;&#x2F;p&gt;
&lt;p&gt;Changing the type of an entity is now as simple as changing some of its fields:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#ffffff;color:#323232;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#795da3;&quot;&gt;become_zombie&lt;&#x2F;span&gt;&lt;span&gt;(entity: &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;&amp;amp;mut&lt;&#x2F;span&gt;&lt;span&gt; GameEntity) {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;font-style:italic;color:#969896;&quot;&gt;&#x2F;&#x2F; replace human ai with zombie ai
&lt;&#x2F;span&gt;&lt;span&gt;    entity.actor_state &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;Some&lt;&#x2F;span&gt;&lt;span&gt;(ActorState::new_zombie_state());
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    entity.human &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;false&lt;&#x2F;span&gt;&lt;span&gt;;
&lt;&#x2F;span&gt;&lt;span&gt;    entity.zombie &lt;&#x2F;span&gt;&lt;span style=&quot;font-weight:bold;color:#a71d5d;&quot;&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#0086b3;&quot;&gt;true&lt;&#x2F;span&gt;&lt;span&gt;;
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The price one pays for this dynamism is there is much more flexibility possible,
not all of it desirable. What would happen if &lt;code&gt;become_zombie&lt;&#x2F;code&gt; was called on a
sword? The programmer must now think about these extra possibilities and
explicitly check for them, rather than the language doing this checking at
compile time. I argue that this is a reasonable trade-off, as you no longer need
to worry about the engine not being flexible enough to express a behaviour
you &lt;em&gt;do&lt;&#x2F;em&gt; want.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;so-far-so-good&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#so-far-so-good&quot; aria-label=&quot;Anchor link for: so-far-so-good&quot;&gt;So far so good&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;The first few game engines I developed used a class hierarchy to represent game
entities, and I was plagued by situations that I couldn’t
represent. Since switching to this approach, I’m yet to encounter such a
situation.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;further-reading&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#further-reading&quot; aria-label=&quot;Anchor link for: further-reading&quot;&gt;Further Reading&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;Representing entities by their constituent parts is how data is represented in
an &lt;a href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;Entity%E2%80%93component%E2%80%93system&quot;&gt;Entity Component
System&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Apocalypse Post</title>
          <pubDate>Sat, 11 Mar 2017 04:20:00 +1000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/apocalypse-post/</link>
          <guid>https://www.gridbugs.org/apocalypse-post/</guid>
          <description xml:base="https://www.gridbugs.org/apocalypse-post/">&lt;p&gt;Apocalypse Post is a procedurally-generated, turn-based tactical shooter set in a post-apocalyptic future, where you carry mail between survivor camps in your trusty delivery van, all the while fending off attacks from bandits and zombies. Between each delivery run, buy weapons and armour to upgrade your van to cope with ever-increasing numbers of enemies.&lt;&#x2F;p&gt;
&lt;p&gt;I made this game for the 2017 7 Day Roguelike game jam.&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;gridbugs.itch.io&#x2F;apocalypse-post&quot;&gt;Download&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;gridbugs&#x2F;apocalypse-post&quot;&gt;Source&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;apocalypse-post&#x2F;screenshot.png&quot; alt=&quot;screenshot.png&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>7 Day Roguelike 2017: Success</title>
          <pubDate>Sat, 11 Mar 2017 00:02:00 +1000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/7drl2017-success/</link>
          <guid>https://www.gridbugs.org/7drl2017-success/</guid>
          <description xml:base="https://www.gridbugs.org/7drl2017-success/">&lt;p&gt;I spent the night polishing, play-testing, balancing and fixing some minor bugs.
I consider the week to have been largely successful. The game turned out roughly
like what I imagined at the start of the week.&lt;&#x2F;p&gt;
&lt;p&gt;The main thing I would do differently if I could do it over would be making
gameplay more pure. My original design was inspired by 868HACK, specifically the
predictable gameplay allowing for thinking many moves ahead. The result I ended
up with was instead quite organic, with gameplay emerging from low-level physical
rules. I still think I have created something fun, but it wasn’t exactly what I
set out to achieve.&lt;&#x2F;p&gt;
&lt;p&gt;Something I’m happy with is the fact that players must think about short and
long term goals when playing Apocalypse Post. During a delivery they make short
term decisions about positioning and combat. Between deliveries, they must
decide whether it’s worth spending money on things they might need next mission,
or saving up for something more expensive that will pay off long term.
Further, while on a mission, players collect letters which increase the reward
at the end of the mission. Going out of their way to collect letters is more
dangerous than just taking the safest route, so there is a trade-off between
safety right now, and having enough resources later on.
This is
an improvement over the game I made for last year’s 7DRL,
&lt;a href=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;skeleton-crew&#x2F;&quot;&gt;Skeleton Crew&lt;&#x2F;a&gt;, in which all the decision making was short
term.&lt;&#x2F;p&gt;
&lt;p&gt;Another goal of this week for me was to stress test the game engine I’ve
been developing for several months. Its goal is to be able to efficiently and
flexibly encode and enforce rules for a turn-based game,
which it does using many concepts
borrowed from Entity Component Systems.&lt;&#x2F;p&gt;
&lt;p&gt;The 7DRL has succeeded in highlighting
which parts of the engine I would most benefit from improving. In short, as the
number of rules increases, unintended interactions between rules can
occasionally cause undesired results. I have some ideas for addressing this
problem. I plan on making some blog posts explaining how the engine works and
how I’ll address the limitations I discovered.&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>7 Day Roguelike 2017: Consumables</title>
          <pubDate>Fri, 10 Mar 2017 03:33:00 +1000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/7drl2017-consumables/</link>
          <guid>https://www.gridbugs.org/7drl2017-consumables/</guid>
          <description xml:base="https://www.gridbugs.org/7drl2017-consumables/">&lt;p&gt;I added many features today, most notably consumable items that can be used
while stationary in the field to repair the engine or replace a burst tyre.
These items can be bought from the shop in the survivor camp. The shop also
sells repairs that happen immediately.&lt;&#x2F;p&gt;
&lt;p&gt;Zombies are now dumber, and the game can handle many more of them.
The game had noticeable delays between turns when there were more than 30
enemies on a map. Profiling revealed most of the time being spent in the zombie
pathfinding code. Each zombie was performing a search to find the shortest path
to the player. This was wasteful, and could be made much faster using dijkstra
maps, where a map describing the shortest path from each cell to the player is
computed once, and used by all NPCs. In the interest of time however, I made
the zombies simply walk to wards the
player if they know where the player is. This means zombies will now bump into
obstacles as they move towards the player, which is appropriate behaviour for
zombies. The game can now handle hundreds of zombies with no noticeable slowdown.&lt;&#x2F;p&gt;
&lt;p&gt;Various other minor changes:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;The contents of the shop is now randomized each time you enter the survivor
camp.&lt;&#x2F;li&gt;
&lt;li&gt;The chance to encounter the more difficult enemies (cars and bikes) goes up as
the game progresses.&lt;&#x2F;li&gt;
&lt;li&gt;The current amount of money owned by the player appears on the hud.&lt;&#x2F;li&gt;
&lt;li&gt;The hud is now visible in menus in the survivor camp.&lt;&#x2F;li&gt;
&lt;li&gt;I added flavour text to the camp explaining that the player is healed and
paid when entering the camp. This should also help establish the game’s
(minimal) story.&lt;&#x2F;li&gt;
&lt;li&gt;The message log is now cleared between delivery runs.&lt;&#x2F;li&gt;
&lt;li&gt;Driving into barrels causes them to explode.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
</description>
      </item>
      <item>
          <title>7 Day Roguelike 2017: Procgen and Explosions</title>
          <pubDate>Thu, 09 Mar 2017 03:03:00 +1000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/7drl2017-procgen-explosions/</link>
          <guid>https://www.gridbugs.org/7drl2017-procgen-explosions/</guid>
          <description xml:base="https://www.gridbugs.org/7drl2017-procgen-explosions/">&lt;p&gt;Today I implemented a procedural generator. Levels are relatively simple,
compared to say, dungeons. Levels have a straight road, with acid generated
using &lt;a href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;Perlin_noise&quot;&gt;Perlin Noise&lt;&#x2F;a&gt;, and various
enemies, obstacles and items placed randomly, with different probabilities for
each entity starting in a cell for road cells and off-road cells.&lt;&#x2F;p&gt;
&lt;p&gt;Here’s a screenshot from a procedurally generated level.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;7drl2017-procgen-explosions&#x2F;screenshot.png&quot; alt=&quot;screenshot.png&quot; &#x2F;&gt;&lt;&#x2F;p&gt;</description>
      </item>
      <item>
          <title>7 Day Roguelike 2017: Mechanics and Descriptions</title>
          <pubDate>Wed, 08 Mar 2017 01:44:00 +1000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/7drl2017-mechanics/</link>
          <guid>https://www.gridbugs.org/7drl2017-mechanics/</guid>
          <description xml:base="https://www.gridbugs.org/7drl2017-mechanics/">&lt;p&gt;Today I implemented lots of small mechanics to add some depth to gameplay. I
also added descriptions to many actions which are printed in the message log.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;7drl2017-mechanics&#x2F;screenshot.png&quot; alt=&quot;screenshot.png&quot; &#x2F;&gt;&lt;&#x2F;p&gt;</description>
      </item>
      <item>
          <title>7 Day Roguelike 2017: Vehicles</title>
          <pubDate>Tue, 07 Mar 2017 02:41:00 +1000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/7drl2017-vehicles/</link>
          <guid>https://www.gridbugs.org/7drl2017-vehicles/</guid>
          <description xml:base="https://www.gridbugs.org/7drl2017-vehicles/">&lt;p&gt;Today I added cars and motorbikes. Cars are equipped with shotguns and can
shoot to the left and right. Bikes are equipped with a pistol and can shoot in
all directions.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;7drl2017-vehicles&#x2F;screenshot.png&quot; alt=&quot;screenshot.png&quot; &#x2F;&gt;&lt;&#x2F;p&gt;</description>
      </item>
      <item>
          <title>7 Day Roguelike 2017: Shopping</title>
          <pubDate>Mon, 06 Mar 2017 02:24:00 +1000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/7drl2017-shopping/</link>
          <guid>https://www.gridbugs.org/7drl2017-shopping/</guid>
          <description xml:base="https://www.gridbugs.org/7drl2017-shopping/">&lt;p&gt;The main feature I implemented today was a set of menus that are displayed
between levels. These include a shop for buying items (currently just guns),
equipping guns to weapon slots (which can’t be done in the field), and
inventory management (just removing items from the inventory to make room for
more items).&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;7drl2017-shopping&#x2F;shop.png&quot; alt=&quot;shop.png&quot; &#x2F;&gt;&lt;&#x2F;p&gt;</description>
      </item>
      <item>
          <title>7 Day Roguelike 2017: Day 1</title>
          <pubDate>Sun, 05 Mar 2017 03:22:00 +1000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/7drl2017-day1/</link>
          <guid>https://www.gridbugs.org/7drl2017-day1/</guid>
          <description xml:base="https://www.gridbugs.org/7drl2017-day1/">&lt;p&gt;After a day of development, I’ve implemented some of the basic functionality and
created some of the artwork. You can control the speed of the van, and steer it
provided that its speed is above zero. Zombies can be run over which insta-kills
them, or they can be shot if they are directly in front of the van.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;7drl2017-day1&#x2F;screenshot.png&quot; alt=&quot;screenshot.png&quot; &#x2F;&gt;&lt;&#x2F;p&gt;</description>
      </item>
      <item>
          <title>7 Day Roguelike 2017: Plan</title>
          <pubDate>Sat, 04 Mar 2017 09:54:00 +1000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/7drl2017-plan/</link>
          <guid>https://www.gridbugs.org/7drl2017-plan/</guid>
          <description xml:base="https://www.gridbugs.org/7drl2017-plan/">&lt;p&gt;For this year’s &lt;a href=&quot;http:&#x2F;&#x2F;7drl.roguetemple.com&#x2F;&quot;&gt;7DRL&lt;&#x2F;a&gt; I’m making a game called
“Apocalypse Post”. It will be a &lt;a href=&quot;http:&#x2F;&#x2F;www.roguebasin.com&#x2F;index.php?title=Category:Coffeebreak_roguelikes&quot;&gt;coffeebreak
roguelike&lt;&#x2F;a&gt;
where the player delivers mail in a post-apocalyptic world.&lt;&#x2F;p&gt;
&lt;p&gt;I’m writing the game in rust, using &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;gridbugs&#x2F;howl&quot;&gt;Howl&lt;&#x2F;a&gt; as
a starting point. My recent focus for Howl has been adding basic (but
non-trivial) functionality to the engine (menus, saving, configuring controls)
so I can take advantage of it for the 7DRL and focus on the high-level parts of
game development this week.&lt;&#x2F;p&gt;
&lt;p&gt;Features I plan to implement:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;on each turn the player automatically moves forward&lt;&#x2F;li&gt;
&lt;li&gt;gun-based combat&lt;&#x2F;li&gt;
&lt;li&gt;various weapons with interesting tactical significance (e.g. rail gun shoots
through everything in a certain direction, costing you a finite resource)&lt;&#x2F;li&gt;
&lt;li&gt;a shop for buying upgrades and equipment&lt;&#x2F;li&gt;
&lt;li&gt;procedural terrain generator suitable for automatic side-scrolling&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;I’ll try to post daily updates to this site.&lt;&#x2F;p&gt;
&lt;p&gt;Follow development more closely on
&lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;gridbugs&#x2F;apocalypse-post&quot;&gt;github&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Howl</title>
          <pubDate>Sat, 07 Jan 2017 00:00:00 +1000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/howl/</link>
          <guid>https://www.gridbugs.org/howl/</guid>
          <description xml:base="https://www.gridbugs.org/howl/">&lt;p&gt;Howl is a turn-based tactical game, where everything has an alternate form that
is revealed under moonlight. I started this project to teach myself &lt;a href=&quot;https:&#x2F;&#x2F;www.rust-lang.org&quot;&gt;the rust
programming language&lt;&#x2F;a&gt;, and to experiment with
&lt;a href=&quot;https:&#x2F;&#x2F;wikipedia.org&#x2F;wiki&#x2F;Entity-component-system&quot;&gt;entity component systems&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;The game is still in active development.
Check out the &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;gridbugs&#x2F;howl&quot;&gt;source code on github&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Note that the linux version of the game depends on sdl2, sdl2_image, and sdl2_ttf.&lt;&#x2F;p&gt;
&lt;p&gt;Controls:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;movement: arrow keys&lt;&#x2F;li&gt;
&lt;li&gt;fire: f, f again to fire, arrow keys move cursor, n&#x2F;m switch targets&lt;&#x2F;li&gt;
&lt;li&gt;close door: c followed by arrow key&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;howl&#x2F;screenshot.png&quot; alt=&quot;screenshot.png&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Roguelike Lighting Demo</title>
          <pubDate>Tue, 20 Dec 2016 09:31:00 +1000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/roguelike-lighting-demo/</link>
          <guid>https://www.gridbugs.org/roguelike-lighting-demo/</guid>
          <description xml:base="https://www.gridbugs.org/roguelike-lighting-demo/">&lt;p&gt;This is a demonstration of some lighting techniques I’ve been experimenting
with. It was originally intended to be an entire game, but I’ve abandoned it to
work on other projects.&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;games.gridbugs.org&#x2F;roguelike-lighting-demo&quot;&gt;Run in browser&lt;&#x2F;a&gt;
&lt;ul&gt;
&lt;li&gt;Use arrow or vi keys to move. Press “f” followed by a direction key to fire a
burst of plasma.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;gridbugs&#x2F;roguelike-lighting-demo&quot;&gt;Source code on github&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;roguelike-lighting-demo&#x2F;screenshot0.png&quot; alt=&quot;screenshot0.png&quot; &#x2F;&gt;&lt;&#x2F;p&gt;</description>
      </item>
      <item>
          <title>Even Separation Algorithm</title>
          <pubDate>Wed, 04 May 2016 23:27:00 +1000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/even-separation-algorithm/</link>
          <guid>https://www.gridbugs.org/even-separation-algorithm/</guid>
          <description xml:base="https://www.gridbugs.org/even-separation-algorithm/">&lt;p&gt;This post describes an algorithm for evenly spreading out a sequence of items made
up of
two distinct types of item. I came up with it when attempting to draw straight
lines on a grid, where lines are represented by discrete steps in one of two
directions. In order for such a line to appear straight, the steps in one
direction should be spread out as much as possible with respect to the steps in
the other direction. The solution generalizes to spreading out any sequence
made up of two distinct types of item that are repeated a number of times.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;even-separation-algorithm&#x2F;straight-line-screenshot.png&quot; alt=&quot;straight-line-screenshot.png&quot; &#x2F;&gt;&lt;&#x2F;p&gt;</description>
      </item>
      <item>
          <title>Skeleton Crew</title>
          <pubDate>Sat, 12 Mar 2016 00:19:00 +1000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/skeleton-crew/</link>
          <guid>https://www.gridbugs.org/skeleton-crew/</guid>
          <description xml:base="https://www.gridbugs.org/skeleton-crew/">&lt;p&gt;I wrote this game for the 2016 7 Day Roguelike challenge.&lt;&#x2F;p&gt;
&lt;p&gt;Fight a variety of undead enemies with a variety of weapons on a procedurally
generated spaceship. Damaging the hull of the ship causes sections of the ship
to be depressurized, and their contents sucked into space, including you!&lt;&#x2F;p&gt;
&lt;p&gt;Play or download Skeleton Crew on &lt;a href=&quot;https:&#x2F;&#x2F;gridbugs.itch.io&#x2F;skeleton-crew&quot;&gt;its itch.io page&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;View the &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;gridbugs&#x2F;skeleton-crew&quot;&gt;source code on github&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;skeleton-crew&#x2F;screenshot.png&quot; alt=&quot;screenshot.png&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>7 Day Roguelike 2016: Success</title>
          <pubDate>Sat, 12 Mar 2016 00:17:00 +1000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/7drl2016-success/</link>
          <guid>https://www.gridbugs.org/7drl2016-success/</guid>
          <description xml:base="https://www.gridbugs.org/7drl2016-success/">&lt;p&gt;It’s now Friday night (technically Saturday morning). Tomorrow morning it will have been 1 week since I
started work on “Skeleton Crew”. This is the final entry in my development log.&lt;&#x2F;p&gt;
&lt;p&gt;Tonight was mostly polishing. I added support for arrow keys and the numpad as
an alternative to vi keys, lots of playtesting and tweaked a few parameters here
and there to make the game more balanced.&lt;&#x2F;p&gt;
&lt;p&gt;I also added a rocket launcher.&lt;&#x2F;p&gt;
&lt;p&gt;Aiming. This also happens to show another rocket launcher lying on the ground.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;7drl2016-success&#x2F;s0.png&quot; alt=&quot;s0.png&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Fired!&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;7drl2016-success&#x2F;s1.png&quot; alt=&quot;s1.png&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Explosion! This shows the shock wave beginning to move outwards. The nearest
zombies have already been pushed back by the explosion.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;7drl2016-success&#x2F;s2.png&quot; alt=&quot;s2.png&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Aftermath.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;7drl2016-success&#x2F;s3.png&quot; alt=&quot;s3.png&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>7 Day Roguelike 2016: Ending</title>
          <pubDate>Fri, 11 Mar 2016 02:08:00 +1000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/7drl2016-ending/</link>
          <guid>https://www.gridbugs.org/7drl2016-ending/</guid>
          <description xml:base="https://www.gridbugs.org/7drl2016-ending/">&lt;p&gt;Tonight I implemented stairs and an ending for the game. You must reach the
teleporter on the 3rd floor of the ship, and teleport to safety. I also added
some more flavour text, fixed some bugs, and balanced some characters. At this
point the game is largely complete. I’ll spend some of tomorrow playtesting and
adding any remaining polish and balance I think is necessary. I also want to
add a rocket launcher which shouldn’t be too complicated given I already have
projectile weapons and explosions.&lt;&#x2F;p&gt;
&lt;p&gt;This screenshot shows the teleporter at the end of the game:
&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;7drl2016-ending&#x2F;screenshot.png&quot; alt=&quot;screenshot.png&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>7 Day Roguelike 2016: Polish</title>
          <pubDate>Thu, 10 Mar 2016 02:18:00 +1000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/7drl2016-polish/</link>
          <guid>https://www.gridbugs.org/7drl2016-polish/</guid>
          <description xml:base="https://www.gridbugs.org/7drl2016-polish/">&lt;p&gt;Tonight I finished the level generator, adding weapons and items.
I fixed a bug in my recursive shadowcast implementation causing strange
behaviour when looking at the edge of the map.
I tweaked a
couple of mechanics, namely:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;explosions destroy nearby walls regardless of whether one side is pressurized
&lt;ul&gt;
&lt;li&gt;this is unlike bullets, which  only damage walls if one side is
pressurized and the other isn’t&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;flames from the flamethrower don’t stop when they hit an enemy&lt;&#x2F;li&gt;
&lt;li&gt;doors can’t be closed if there is an enemy standing in the way&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;I spent the rest of my time tonight polishing the game’s UI.&lt;&#x2F;p&gt;
&lt;p&gt;Here are some death screens from playtesting!&lt;&#x2F;p&gt;
&lt;p&gt;Shooting a bloat at point-blank range. Never a good idea.
&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;7drl2016-polish&#x2F;s0.png&quot; alt=&quot;s0.png&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Tried to kill some zombies by blowing up a nearby bloat. Didn’t end well.
&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;7drl2016-polish&#x2F;s1.png&quot; alt=&quot;s1.png&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Getting sucked out of the ship as a section decompresses due to a hull breach.
&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;7drl2016-polish&#x2F;s2.png&quot; alt=&quot;s2.png&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>7 Day Roguelike 2016: Pathfinding</title>
          <pubDate>Wed, 09 Mar 2016 01:56:00 +1000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/7drl2016-pathfinding/</link>
          <guid>https://www.gridbugs.org/7drl2016-pathfinding/</guid>
          <description xml:base="https://www.gridbugs.org/7drl2016-pathfinding/">&lt;p&gt;The most notable change from tonight is that
I turned on pathfinding for NPCs for the first time. Up until now they
had been stationary, and could be injured or killed, or affected by vacuum, but
could never observe the world or take actions. Enabling AI is scary because it
greatly increases the amount of work the computer is doing between human turns.
The vision system is now running once per NPC turn as well as the player. Then
there’s the additional cost of pathfinding for each NPC. I use Dijkstra maps for
pathfinding which are
&lt;a href=&quot;http:&#x2F;&#x2F;www.roguebasin.com&#x2F;index.php?title=The_Incredible_Power_of_Dijkstra_Maps&quot;&gt;explained in detail on roguebasin&lt;&#x2F;a&gt;.
This post is about solving a performance problem introduced by all the extra
characters.&lt;&#x2F;p&gt;
&lt;p&gt;With AI turned on, there was a noticeable increase in the time between player
turns.
Benchmarking revealed most of the time between player turns was spent in the
vision system.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;vision-system&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#vision-system&quot; aria-label=&quot;Anchor link for: vision-system&quot;&gt;Vision System&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;The vision system uses the Recursive Shadowcast algorithm which I
&lt;a href=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;visible-area-detection-recursive-shadowcast&#x2F;&quot;&gt;wrote about earlier&lt;&#x2F;a&gt;.
Detecting visible cells
is relatively fast. The problem was what how this information was being used.&lt;&#x2F;p&gt;
&lt;p&gt;NPC pathfinding and the renderer don’t work on the canonical world
representation. This game uses
&lt;a href=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;encoding-rules-for-turn-based-games&#x2F;&quot;&gt;an ECS-based engine described in a previous post&lt;&#x2F;a&gt;,
so more concretely, AI and the
renderer don’t directly access entities or components. Instead, each character
maintains a representation of the world based on what they have observed.
A character’s knowledge is represented by “shadow entities”, made up of
“shadow components”. These were designed to be frequently updated by setting
data in shadow components from “canonical” reference components without allocating
any new objects. This was a technique I learnt while solving performance problems in a
&lt;a href=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;bugcatcher&#x2F;&quot;&gt;game I wrote earlier&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;At the
start of a character’s turn, the vision system runs and updates that character’s
knowledge of the world by updating shadow components.
It was this knowledge updating process that was taking
up most of the time. Knowledge is updated in the following way. It relies on a
spacial hash of entities, and an analogous spacial hash of shadow entities in
the knowledge representation of characters.&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;for each coordinate visible according to recursive shadowcast:
&lt;&#x2F;span&gt;&lt;span&gt;    knowledgeCell = get knowledge spacial hash cell for the coord
&lt;&#x2F;span&gt;&lt;span&gt;    cell = get canonical spacial hash cell for the coord
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    knowledgeCell.clear() # remove all shadow entities from the cell
&lt;&#x2F;span&gt;&lt;span&gt;    for each entity in that cell:
&lt;&#x2F;span&gt;&lt;span&gt;        knowledgeCell.see(entity) # makes a shadow entity
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Knowledge cells re-use shadow entities
and shadow components to prevent invoking the allocator on each cell that is seen.
Despite this, this process was still taking too long. A key insight for
optimization was that most entities in most cells don’t change most of the time.
The only time a knowledge cell needs to be updated is when an entity has entered
or left the corresponding canonical cell, or an entity in the cell changes in a
“knowable” way (ie. a way that will be represented in a character’s knowledge).&lt;&#x2F;p&gt;
&lt;p&gt;With this in mind, the loop becomes:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;for each coordinate visible according to recursive shadowcast:
&lt;&#x2F;span&gt;&lt;span&gt;    knowledgeCell = get knowledge spacial hash cell for the coord
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    if knowledgeCell.isDirty():
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;        cell = get canonical spacial hash cell for the coord
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;        knowledgeCell.clear()
&lt;&#x2F;span&gt;&lt;span&gt;        for each entity in that cell:
&lt;&#x2F;span&gt;&lt;span&gt;            knoweldgeCell.see(entity)
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The &lt;code&gt;isDirty()&lt;&#x2F;code&gt; method compares a “last modified” timestamp on the canonical
spacial hash table cell with a “last observed” timestamp on the knowledge cell.
&lt;strong&gt;A knowledge cell is now only updated if the cell has been changed more recently
than it was last observed.&lt;&#x2F;strong&gt; A complication this change introduced is now the
“last modified” timestamps must be updated whenever a cell’s contents is changed
in a meaningful way.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;windows-and-npcs&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#windows-and-npcs&quot; aria-label=&quot;Anchor link for: windows-and-npcs&quot;&gt;Windows and NPCs&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;I’ve updated the level generator to include windows and NPCs. It also places the
player in a sane starting position, and determines a sane goal position (though
there currently isn’t anything there).&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;7drl2016-pathfinding&#x2F;screenshot.png&quot; alt=&quot;screenshot.png&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Still to do is adding items (weapons and healthkits), and stairs. I still
haven’t decided how the game will end, though the current plan is to have some
emergency beacon that needs to be activated before the ship-full-of-zombies
arrives in a populated area.&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>7 Day Roguelike 2016: Procedural Generation</title>
          <pubDate>Tue, 08 Mar 2016 03:04:00 +1000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/7drl2016-procedural-generation/</link>
          <guid>https://www.gridbugs.org/7drl2016-procedural-generation/</guid>
          <description xml:base="https://www.gridbugs.org/7drl2016-procedural-generation/">&lt;p&gt;This is my second attempt at a procedurally generated space ship. The first
attempt involved generating the hull first by starting with a large rectangle
and stripping smaller rectangular pieces away until I got something roughly
hull-looking. I would then attempt to fill the hull with rooms. Adding rooms to
a preexisting hull proved messy and prone to difficult edge cases.&lt;&#x2F;p&gt;
&lt;p&gt;Here’s what it looked like at the point where I gave up:
&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;7drl2016-procedural-generation&#x2F;old.png&quot; alt=&quot;old.png&quot; &#x2F;&gt;&lt;&#x2F;p&gt;</description>
      </item>
      <item>
          <title>7 Day Roguelike 2016: Day 1</title>
          <pubDate>Sun, 06 Mar 2016 10:04:00 +1000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/7drl2016-day1/</link>
          <guid>https://www.gridbugs.org/7drl2016-day1/</guid>
          <description xml:base="https://www.gridbugs.org/7drl2016-day1/">&lt;p&gt;It’s one day in. Here’s my progress so far!&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;7drl2016-day1&#x2F;screenshot.png&quot; alt=&quot;screenshot.png&quot; &#x2F;&gt;&lt;&#x2F;p&gt;</description>
      </item>
      <item>
          <title>7 Day Roguelike 2016: Plan</title>
          <pubDate>Sat, 05 Mar 2016 10:06:00 +1000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/7drl2016-plan/</link>
          <guid>https://www.gridbugs.org/7drl2016-plan/</guid>
          <description xml:base="https://www.gridbugs.org/7drl2016-plan/">&lt;p&gt;The 7 Day Roguelike Challenge is a game jam where participants make a roguelike in 7 days.
This year I’m making a game called “Skeleton Crew” where you fight undead things
on a spaceship. I’ll post updates to this site as I make progress.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-plan&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#the-plan&quot; aria-label=&quot;Anchor link for: the-plan&quot;&gt;The Plan&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;I’m going to start with the &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;gridbugs&#x2F;glacial&quot;&gt;glacial
codebase&lt;&#x2F;a&gt;.
I plan to implement the following features:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;guns&lt;&#x2F;li&gt;
&lt;li&gt;shooting the hull can cause a breach and vent the atmosphere from connected
parts of the ship&lt;&#x2F;li&gt;
&lt;li&gt;you need oxygen in your suit to survive in vacuum&lt;&#x2F;li&gt;
&lt;li&gt;oxygen drains while
you’re in vacuum, and recharges while you’re in atmosphere.&lt;&#x2F;li&gt;
&lt;li&gt;as atmosphere is vented, characters and items are sucked towards, and possibly
out of, the breach&lt;&#x2F;li&gt;
&lt;li&gt;flamethrower that doesn’t penetrate the hull but only works in atmosphere&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
</description>
      </item>
      <item>
          <title>Encoding Rules for Turn-Based Games</title>
          <pubDate>Wed, 02 Mar 2016 23:20:01 +1000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/encoding-rules-for-turn-based-games/</link>
          <guid>https://www.gridbugs.org/encoding-rules-for-turn-based-games/</guid>
          <description xml:base="https://www.gridbugs.org/encoding-rules-for-turn-based-games/">&lt;p&gt;A key consideration when designing a game engine is how rules of the game will
be encoded. The engine needs a way of enforcing statements such as “Doors can
only be passed through if they are open”, and “If a burning character walks into
a pool of water, it stops burning”. The expressiveness of a game engine’s
rule-encoding is important, as it dictates the limitations of mechanics that can
be implemented in games. Nobody wants to discover late in development that their
engine can’t be used to efficiently implement a certain feature.&lt;&#x2F;p&gt;
&lt;p&gt;This post will describe the framework I developed to encode rules in two games I
recently made: &lt;a href=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;glacial&#x2F;&quot;&gt;Glacial&lt;&#x2F;a&gt; and &lt;a href=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;bugcatcher&#x2F;&quot;&gt;Bugcatcher&lt;&#x2F;a&gt;. It’s based on
the idea of an Entity Component System (ECS), extended with the abstraction of
Actions.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;entity-component-system&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#entity-component-system&quot; aria-label=&quot;Anchor link for: entity-component-system&quot;&gt;Entity Component System&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;Every object in the game (walls,
characters, doors, bullets, etc) is represented by an &lt;strong&gt;entity&lt;&#x2F;strong&gt;. An
entity is simply a container for storing &lt;strong&gt;components&lt;&#x2F;strong&gt;. Components store
information about objects in the world. The fact that an object is solid, that
an object is opaque, that it has health or that it can take actions are all
examples of components. The key idea of ECS is that all the information about
the state of the world is stored in components, and each entity is simply a
collection of various components.&lt;&#x2F;p&gt;
&lt;p&gt;The rules of the game are represented by various systems that operate on
components of entities.
An example of such a system is
a collision detector, that checks if an entity with a &lt;em&gt;Collider&lt;&#x2F;em&gt;
component is about to walk into an entity with a &lt;em&gt;Solid&lt;&#x2F;em&gt; component, and stops the
movement from going ahead.
Another example is a system that applies burning effects. Periodically, it loops
through each entity with a &lt;em&gt;Burning&lt;&#x2F;em&gt; component and a &lt;em&gt;Health&lt;&#x2F;em&gt; component, and reduce
their health by some amount.&lt;&#x2F;p&gt;
&lt;p&gt;This should serve as sufficient background in Entity Component Systems. The
previous paragraph was intentionally vague about the details of how systems
work. Rest assured that a more concrete explanation will follow. To learn more
about ECS:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Wikipedia has an overview: &lt;a href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;Entity_component_system&quot;&gt;Entity component system&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;More detailed description and comparison to OOP:
&lt;a href=&quot;http:&#x2F;&#x2F;www.gamedev.net&#x2F;page&#x2F;resources&#x2F;_&#x2F;technical&#x2F;game-programming&#x2F;understanding-component-entity-systems-r3013&quot;&gt;Understanding
Component-Entity-Systems&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;Another index of references from roguebasin:
&lt;a href=&quot;http:&#x2F;&#x2F;www.roguebasin.com&#x2F;index.php?title=Entity_Component_System&quot;&gt;Entity Component System&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;actions&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#actions&quot; aria-label=&quot;Anchor link for: actions&quot;&gt;Actions&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;So far I’ve explained how wold state is represented - as a
collection of entities made up of components. What’s missing from the picture is
how the world state gets updated. This engine is specifically designed for
turn-based games, so the world is updated in discrete, sequential steps.
This is where &lt;strong&gt;actions&lt;&#x2F;strong&gt; come in: an action
is a description of a change in the world state.
More concretely, actions can:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;change the parameters of components (of entities)&lt;&#x2F;li&gt;
&lt;li&gt;add&#x2F;remove components to&#x2F;from entities&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;The only way the world state can change is by an action being committed.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;systems&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#systems&quot; aria-label=&quot;Anchor link for: systems&quot;&gt;Systems&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;There are several different types of systems for encoding different types of
rule:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Reactive Systems respond to actions&lt;&#x2F;li&gt;
&lt;li&gt;Continuous Systems run periodically to simulate continuous processes&lt;&#x2F;li&gt;
&lt;li&gt;Passive Systems don’t affect the world state, but may update other parts of
the game (e.g. a renderer). The don’t encode game rules, but rather behaviour
of the game itself. As they aren’t particularly interesting in the context of
this post, this is the last I will say about them.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h3 id=&quot;reactive-systems&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#reactive-systems&quot; aria-label=&quot;Anchor link for: reactive-systems&quot;&gt;Reactive Systems&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;Systems can register an interest in certain
types of actions.
When an action is proposed, interested systems can examine it,
and possibly cause additional actions to occur in response, or cancel the current action.
If after all interested systems have had the chance to respond to an action, the
action hasn’t been cancelled, the action is committed, and the change it
describes actually occurs. If during this process, a system
caused additional actions to be scheduled, these actions go through
the same process.&lt;&#x2F;p&gt;
&lt;p&gt;As an example, consider a &lt;em&gt;Walk&lt;&#x2F;em&gt; action. When the action is proposed (by a
character’s AI or the player), it contains information about
how the walk will occur, such as the start coordinate, the destination
coordinate, the direction and the entity (character) who is walking. The
&lt;em&gt;Collision&lt;&#x2F;em&gt; system has registered an interest in &lt;em&gt;Walk&lt;&#x2F;em&gt; actions. When it sees one,
it checks to see if the entity who is walking has the &lt;em&gt;Collider&lt;&#x2F;em&gt; component
(indicating it’s not, say, a ghost who can walk through walls), and if the
destination coordinate contains any entity with the &lt;em&gt;Solid&lt;&#x2F;em&gt; component. If these
conditions are met, the &lt;em&gt;Collision&lt;&#x2F;em&gt; system cancels the &lt;em&gt;Walk&lt;&#x2F;em&gt; action.&lt;&#x2F;p&gt;
&lt;p&gt;If an action is canceled, it is still presented to all other
interested systems. Systems have the ability to check if an action has been
cancelled, so if multiple systems conflict, this can be resolved in
the systems themselves, and by the game designer specifying the order in which
systems run. Consider a &lt;em&gt;SpiderWeb&lt;&#x2F;em&gt; system, that when presented with a &lt;em&gt;Walk&lt;&#x2F;em&gt;
action where the walker is stuck in a spider web (which can be represented by their
entity having a &lt;em&gt;StuckInWeb&lt;&#x2F;em&gt; component), the walk is cancelled and another
action is scheduled that breaks the spider web. If a character who is stuck in
a web walks into a wall, the first system to run out of the &lt;em&gt;StuckInWeb&lt;&#x2F;em&gt; system
and the &lt;em&gt;Collision&lt;&#x2F;em&gt; system will cancel the action. It wouldn’t make much sense to
have the player struggle in the web, not moving, but then to also bump into a
wall. The decision of what should happen in this situation is up to the game
designer, and they are free to order systems as they please, and to check if an
action is cancelled before processing it.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;continuous-systems&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#continuous-systems&quot; aria-label=&quot;Anchor link for: continuous-systems&quot;&gt;Continuous Systems&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;Continuous systems simulate continuous processes, such as recovering health over
time.
At the end of each turn, all continuous systems are invoked with the amount of
time that has passed since they were last invoked. This information, coupled
with a rate of change, can be used to change values in away that
appears continuous.&lt;&#x2F;p&gt;
&lt;p&gt;What actually happens when a continuous system runs is up to the implementation.
Typically, it will iterate over all entities with some component, and schedule
an action for the entity based on a rate of change specified by the component,
and the time delta since the last time the system ran.&lt;&#x2F;p&gt;
&lt;p&gt;Consider a mechanic where characters that are on fire take a certain amount of
damage each second. An &lt;em&gt;OnFire&lt;&#x2F;em&gt; component could be used to signify that an
entity is on fire. It could be parameterized with a &lt;em&gt;rate&lt;&#x2F;em&gt;, indicating the
damage taken per second. A &lt;em&gt;Burning&lt;&#x2F;em&gt; continuous system would iterate over all
the entities with an &lt;em&gt;OnFire&lt;&#x2F;em&gt; component, and for each entity, schedule an action
where the entity takes &lt;code&gt;timeDelta * damageRate&lt;&#x2F;code&gt; damage.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;schedule-and-game-loop&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#schedule-and-game-loop&quot; aria-label=&quot;Anchor link for: schedule-and-game-loop&quot;&gt;Schedule and Game Loop&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;The engine uses a schedule to keep track of the order of turns.
Tasks can be added to the schedule, along with a relative time at which
they should occur. The schedule can be queried for the next task. It keeps track
of the current absolute time, which increases as tasks are retrieved from the
schedule.
The game loop is based solely on the schedule, and isn’t
particularly interesting:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;while the game is not over {
&lt;&#x2F;span&gt;&lt;span&gt;    get next task from schedule;
&lt;&#x2F;span&gt;&lt;span&gt;    do task;
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;More interesting, is the contents of tasks that are scheduled.
The engine schedules two types of tasks: turns and immediate actions.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;turn&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#turn&quot; aria-label=&quot;Anchor link for: turn&quot;&gt;Turn&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;During a turn, an entity produces a Turn object, consisting of:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;action: an action to perform&lt;&#x2F;li&gt;
&lt;li&gt;time: the time this action will take&lt;&#x2F;li&gt;
&lt;li&gt;reschedule: whether the entity should be rescheduled on the current schedule&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Entities that can take turns have the &lt;em&gt;TurnTaker&lt;&#x2F;em&gt; component. This component
contains a function that returns a Turn object describing what the entity does
on its turn.
For NPCs, this function constructs a turn based on the behaviour of the entity.
For the player character, it returns a turn based on the player’s
input.&lt;&#x2F;p&gt;
&lt;p&gt;A turn progresses as follows:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#ffffff;color:#323232;&quot;&gt;&lt;code&gt;&lt;span&gt;takeTurn(entity) {
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    turn = entity.getComponent(TurnTaker).takeTurn();
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    for each reactive system {
&lt;&#x2F;span&gt;&lt;span&gt;        run the system on turn.action;
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    if turn.action has not been cancelled {
&lt;&#x2F;span&gt;&lt;span&gt;        turn.action.commit();
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    if turn.reschedule {
&lt;&#x2F;span&gt;&lt;span&gt;        schedule turn for entity in turn.time;
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    for each continuous system {
&lt;&#x2F;span&gt;&lt;span&gt;        timeDelta = schedule.getTimeDelta();
&lt;&#x2F;span&gt;&lt;span&gt;        run the system with timeDelta;
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h3 id=&quot;immediate-actions&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#immediate-actions&quot; aria-label=&quot;Anchor link for: immediate-actions&quot;&gt;Immediate Actions&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;If during a turn, a character’s action caused additional actions to be
scheduled, they must be processed before the next character’s turn. A naive way
to implement this would be to schedule the actions at the current time. This
won’t necessarily work, as it’s possible that the next character’s turn is also
scheduled for the current time. The schedule breaks ties by choosing the first
task to be scheduled out of all those with equal times, so this will result in
these actions being processed after the next character’s turn.&lt;&#x2F;p&gt;
&lt;p&gt;Scheduling actions with negative relative times would result in them being
retrieved from the schedule before anything else. This would lead to further
complications, as the absolute time tracked by the schedule would appear to be
going in reverse.&lt;&#x2F;p&gt;
&lt;p&gt;The solution I implemented was to add an &lt;code&gt;immediate&lt;&#x2F;code&gt; flag to scheduler tasks.
Tasks with this flag set to true will be scheduled before tasks with it set to
false, regardless of their scheduled times. To prevent complications with
tracking absolute time, the schedule ignores immediate tasks when updating
the absolute time. The scheduled times of immediate tasks is used solely to
specify the order in which tasks will be retrieved from the scheduler.&lt;&#x2F;p&gt;
&lt;p&gt;An immediate action is a task that applies an action, after passing it to each
reactive system.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;optimizations&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#optimizations&quot; aria-label=&quot;Anchor link for: optimizations&quot;&gt;Optimizations&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;In order to write efficient systems, it’s important to consider how entities and
components are stored.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;entity-implementation&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#entity-implementation&quot; aria-label=&quot;Anchor link for: entity-implementation&quot;&gt;Entity Implementation&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;An entity consists of an array with one element for each type of component in the
game. Each component type is assigned a unique identifier (an integer) which is
used as an index into this array. Each element of the array corresponds to a
particular type of component. To add a component to an entity, a reference to
the component is placed in the array element corresponding to the component’s
type. To remove a component, the element is set to null.
This allows components of entities
to be retrieved in constant time. A limitation of this representation of
entities is that each entity can have at most one of each type of component.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;spacial-hashing-on-position&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#spacial-hashing-on-position&quot; aria-label=&quot;Anchor link for: spacial-hashing-on-position&quot;&gt;Spacial Hashing on Position&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;As with all information, an entity’s position is just another component. For
games that take place within a 2D grid, the &lt;em&gt;Position&lt;&#x2F;em&gt; component is
parameterized by an (x, y) coordinate. As it is a common operation to check what
is at a particular position within the world, storing all entities that have a
&lt;em&gt;Position&lt;&#x2F;em&gt; component in a spacial hash table, keyed by their position, is a useful
optimization.&lt;&#x2F;p&gt;
&lt;p&gt;Each cell of the spacial hash table is a set of entities. A common operation performed
by systems is checking if any entity at a given coordinate has a certain
component. For example, when checking for collisions, it doesn’t matter which entity at the
destination coordinate is solid, as long as at least one is solid. To prevent having
to loop over a cell’s contents to search for a given component, each cell in the
spacial hash table maintains a count of which components are there. When an
entity enters or leaves the cell, the count for each component is updated based
on that entity’s components.&lt;&#x2F;p&gt;
&lt;p&gt;Components can be added and removed from entities, and entities can be added and
removed from the game. Additionally, entities can move around. All these
operations need to be dealt with to keep the spacial hash up to date.&lt;&#x2F;p&gt;
&lt;p&gt;Each component is given &lt;code&gt;onAdd&lt;&#x2F;code&gt; and &lt;code&gt;onRemove&lt;&#x2F;code&gt; methods, which are called when
the component is added to and removed from an entity respectively. By default,
these are used to maintain in each component, a reference to the entity it is
currently a part of. These methods can be extended for the &lt;em&gt;Position&lt;&#x2F;em&gt; component to add
or remove the entity possessing the component from its current cell in the
spacial hash table.&lt;&#x2F;p&gt;
&lt;p&gt;If an entity is removed from the game, the &lt;code&gt;onRemove&lt;&#x2F;code&gt; method of each of
its components is called. If an entity that isn’t currently part of the game has
a component added to it, the call to &lt;code&gt;onAdd&lt;&#x2F;code&gt; is deferred to when the entity is
added to the game, at which point the &lt;code&gt;onAdd&lt;&#x2F;code&gt; method of each component is
called.&lt;&#x2F;p&gt;
&lt;p&gt;The coordinates stored in a &lt;em&gt;Position&lt;&#x2F;em&gt; component are updated by a method call,
rather than setting properties
of the component directly. This method takes care of removing the entity from its
previous cell in the spacial hash table, and inserting it into its new cell.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;continuous-system-storage&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#continuous-system-storage&quot; aria-label=&quot;Anchor link for: continuous-system-storage&quot;&gt;Continuous System Storage&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;To optimize continuous systems, a set of entities each system is currently
interested in is maintained. For each continuous system, there is a component
such that the system is interested in an entity if and only if it has that
component. The adding and removing of entities from a system’s set of interested
entities is done by the component’s &lt;code&gt;onAdd&lt;&#x2F;code&gt; and &lt;code&gt;onRemove&lt;&#x2F;code&gt; method.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#conclusion&quot; aria-label=&quot;Anchor link for: conclusion&quot;&gt;Conclusion&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;Everything in the game is an entity, and an entity
is just a collection of components. Everything that happens in the game is an
action. Reactive systems react to actions by scheduling more actions, and
continuous systems schedule actions that simulate continuous processes.
These abstractions are expressive enough to encode complex game rules, and can
be implemented efficiently.&lt;&#x2F;p&gt;
&lt;p&gt;An implementation of all the abstractions described in this post can be found in
the &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;gridbugs&#x2F;glacial&quot;&gt;source code of Glacial&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Glacial</title>
          <pubDate>Tue, 01 Mar 2016 01:27:01 +1000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/glacial/</link>
          <guid>https://www.gridbugs.org/glacial/</guid>
          <description xml:base="https://www.gridbugs.org/glacial/">&lt;p&gt;A roguelike I made in Februrary 2016.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;a href=&quot;https:&#x2F;&#x2F;games.gridbugs.org&#x2F;glacial&quot;&gt;Play in browser&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;p&gt;You are a faithful servant of the Pyro God.
You returned to the former home of your ancestors in search of his ancient cathedral,
only to find the city a frozen ruin.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;glacial&#x2F;screenshot.png&quot; alt=&quot;screenshot.png&quot; &#x2F;&gt;&lt;&#x2F;p&gt;</description>
      </item>
      <item>
          <title>Bug Catcher</title>
          <pubDate>Tue, 09 Feb 2016 23:49:01 +1000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/bugcatcher/</link>
          <guid>https://www.gridbugs.org/bugcatcher/</guid>
          <description xml:base="https://www.gridbugs.org/bugcatcher/">&lt;p&gt;This is my first attempt at writing &lt;a href=&quot;http:&#x2F;&#x2F;www.onegameamonth.com&#x2F;&quot;&gt;one game a month&lt;&#x2F;a&gt;. It’s a turn-based
dungeon crawler in the style of traditional roguelikes. All the characters in the game are bugs. Each bug
has an ability and combat stats. You can “channel” a bug and gain access to its ability and stats.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;a href=&quot;https:&#x2F;&#x2F;games.gridbugs.org&#x2F;bugcatcher&quot;&gt;Play in browser&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;bugcatcher&#x2F;screenshot.png&quot; alt=&quot;screenshot.png&quot; &#x2F;&gt;&lt;&#x2F;p&gt;</description>
      </item>
      <item>
          <title>Visible Area Detection with Recursive Shadowcast</title>
          <pubDate>Mon, 16 Nov 2015 12:30:01 +1000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/visible-area-detection-recursive-shadowcast/</link>
          <guid>https://www.gridbugs.org/visible-area-detection-recursive-shadowcast/</guid>
          <description xml:base="https://www.gridbugs.org/visible-area-detection-recursive-shadowcast/">&lt;p&gt;Most games employ some form of visible area detection to simulate the fact that
opaque objects obscure one’s view of whatever is behind them.
&lt;em&gt;Recursive Shadowcast&lt;&#x2F;em&gt; is one
of a handful of algorithms that compute visible area in worlds represented
by 2D grids.
This makes it suitable for use in roguelikes.
This post will explain the recursive shadowcast algorithm.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;visible-area-detection-recursive-shadowcast&#x2F;dcss0.png&quot; alt=&quot;Screenshot from Dungeon Crawl Stone Soup demonstrating its visible area detection&quot; &#x2F;&gt;&lt;&#x2F;p&gt;</description>
      </item>
      <item>
          <title>Cellular Automata Cave Generation</title>
          <pubDate>Sat, 26 Sep 2015 12:30:01 +1000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/cellular-automata-cave-generation/</link>
          <guid>https://www.gridbugs.org/cellular-automata-cave-generation/</guid>
          <description xml:base="https://www.gridbugs.org/cellular-automata-cave-generation/">&lt;p&gt;A cellular automata is a collection of cells whose states change over time
based on the states of adjacent cells.
They can be used to produce natural-looking patterns, such as the cave
in the picture below.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;cellular-automata-cave-generation&#x2F;caverns.png&quot; alt=&quot;caverns.png&quot; &#x2F;&gt;&lt;&#x2F;p&gt;</description>
      </item>
      <item>
          <title>2D Phong Illumination in WebGL</title>
          <pubDate>Mon, 13 Jul 2015 23:49:01 +1000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/2d-phong-illumination-in-webgl/</link>
          <guid>https://www.gridbugs.org/2d-phong-illumination-in-webgl/</guid>
          <description xml:base="https://www.gridbugs.org/2d-phong-illumination-in-webgl/">&lt;p&gt;Suppose you’re rendering an uneven surface like a cobblestone floor, water or
grass.
You could just draw the details on a flat image by hand.
This might look great from one particular angle, but if the player is moving
around, the flatness of the image may be quickly exposed. This is exacerbated by
the presence of lights, which will illuminate the surface as if it had just been
painted on (which it sort of has been).&lt;&#x2F;p&gt;
&lt;p&gt;Another approach is to create lots of polygons and model the surface in 3D.
This will solve the lighting problem (provided you have shaders aware of
lighting), but as these surfaces can have lots of tiny details, that’s lots of
work for you to define all the polygons, and lots of work for your GPU to draw
them, and all you really gain is a nice aesthetic effect.&lt;&#x2F;p&gt;
&lt;p&gt;Another approach that is generally more efficient on graphics hardware is
creating maps - buffers that store information about the details of a surface
relevant to lighting. These maps correspond pixel by pixel to the texture being
drawn onto the surface, and are used when shading fragments (pixels) to
determine exactly how light should behave.&lt;&#x2F;p&gt;
&lt;p&gt;A demo that uses this technique is &lt;a href=&quot;https:&#x2F;&#x2F;games.gridbugs.org&#x2F;old-webgl-shader-demos&#x2F;tiles&#x2F;&quot;&gt;here&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;For the tiles demo, I use two maps. The &lt;strong&gt;bump map&lt;&#x2F;strong&gt; stores the surface normal
(vector at right angle to the surface at a point)
and depth
at every pixel on the screen (or every pixel in a tile since the tiles are
repeated). The &lt;strong&gt;light map&lt;&#x2F;strong&gt; stores values indicating how reflective each pixel
is to ambient, diffuse and specular lights.&lt;&#x2F;p&gt;
&lt;p&gt;Soon I’ll explain exactly how these maps work, but for it to make sense you need
a crash course on lighting.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;crash-course-on-lighting&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#crash-course-on-lighting&quot; aria-label=&quot;Anchor link for: crash-course-on-lighting&quot;&gt;Crash course on lighting&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;I use a technique called
&lt;a href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;Phong_reflection_model&quot;&gt;Phong Illumination&lt;&#x2F;a&gt;
to light the scene. It combines
ambient, diffuse and specular lighting at each pixel.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Ambient&lt;&#x2F;strong&gt; lighting is the same at every pixel. A scene has a global value
representing the amount of ambient light present. Different surfaces may
reflect a different amount of ambient light. It does not change with the viewing
angle.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;2d-phong-illumination-in-webgl&#x2F;ambient_example.png&quot; alt=&quot;ambient_example.png&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Diffuse&lt;&#x2F;strong&gt; lighting is light from a point light source hitting a surface and
illuminating it. The amount of light reflected by a point on a surface is
dependant on the angle between the light source and surface normal at that
point. Here’s a diagram:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;2d-phong-illumination-in-webgl&#x2F;diffuse_diagram.png&quot; alt=&quot;diffuse_diagram.png&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;The more similar the two vectors, the brighter the light. This is computed in
practice by multiplying the intensity of the light by the dot product of the two
vectors. This value is then multiplied by the surface’s diffuse reflection
coefficient, thus different surfaces may reflect a different amount of diffuse
light. If there are multiple light sources in a scene, compute the diffuse
intensity for each light and add them together.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;2d-phong-illumination-in-webgl&#x2F;diffuse_example.png&quot; alt=&quot;diffuse_example.png&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Specular&lt;&#x2F;strong&gt; lighting computes the “shiny” bits of a surface. When you look at polished wood, metal
or water, and see the really bright patches of light reflected on them, these
are specular highlights. The intensity of specular lighting at a point is dependent on the
difference between the angle from a ray reflected from the light at that point
and a vector from that point to the eye.
In the diagram below, the relevant vectors are coloured red and green.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;2d-phong-illumination-in-webgl&#x2F;specular_diagram.png&quot; alt=&quot;specular_diagram.png&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;The intensity of the light is the dot product of the two relevant vectors raised
to some power. The higher the power, the smaller and more intense the highlights
appear, and thus the shinier the surface looks. Multiply this value by the
surfaces specular reflection coefficient and light brightness. If there are
multiple specular lights in an area, compute the specular intensity for each and
add them together.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;2d-phong-illumination-in-webgl&#x2F;specular_example.png&quot; alt=&quot;specular_example.png&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Once the intensity of each type of lighting is computed for a point, just add
them all together to get the total lighting at that point. In the tile example,
I add the ambient and diffuse lighting first, multiply this by the colour of the
pixel (given by the texture) treating the (r, g, b, a) values as a 4D vector,
then add on the specular lighting as a vector (i, i, i, 0) where ‘i’ is the
specular light intensity. This is because I wanted the specular highlights to
appear white rather than draw from the underlying colour.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;2d-phong-illumination-in-webgl&#x2F;phong_example.png&quot; alt=&quot;phong_example.png&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;h2 id=&quot;map-encoding-scheme&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#map-encoding-scheme&quot; aria-label=&quot;Anchor link for: map-encoding-scheme&quot;&gt;Map Encoding Scheme&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;I store maps in bitmap files that I made using GIMP.
Information is encoded in the rgb values
of each pixel.
Each channel (red, green, blue) of a pixel is represented by a single byte. Thus
there are 256 values (0-255) that can be stored in a channel of a pixel.&lt;&#x2F;p&gt;
&lt;p&gt;There are actually 4 images that get combined into making the
tile demo. These are:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;texture&lt;&#x2F;li&gt;
&lt;li&gt;bump map&lt;&#x2F;li&gt;
&lt;li&gt;light map&lt;&#x2F;li&gt;
&lt;li&gt;shine map&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h3 id=&quot;tile-texture&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#tile-texture&quot; aria-label=&quot;Anchor link for: tile-texture&quot;&gt;Tile Texture&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;2d-phong-illumination-in-webgl&#x2F;tile.png&quot; alt=&quot;tile.png&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;A simple texture. It’s used to determine the colour of each pixel.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;bump-map&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#bump-map&quot; aria-label=&quot;Anchor link for: bump-map&quot;&gt;Bump Map&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;2d-phong-illumination-in-webgl&#x2F;bump_map.png&quot; alt=&quot;bump_map.png&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;For each pixel, this encodes the surface normal vector and depth at that pixel.
Normal vectors are represented by a pair of angles. The diagram below shows the
pair of angles used to represent the green point. The horizontal angle is blue
and the vertical angle is red. The vertical angle in this system is constrained
between 90° and -90°. As the tile scene is viewed from above, for the purposes
of this example, the vertical angle will be constrained between 90° and 0°. The
length of normal vectors is always 1.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;2d-phong-illumination-in-webgl&#x2F;angles.png&quot; alt=&quot;angles.png&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Different information is encoded in each channel, so it makes sense to examine
one channel at a time.&lt;&#x2F;p&gt;
&lt;h4 id=&quot;red-horizontal-angle&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#red-horizontal-angle&quot; aria-label=&quot;Anchor link for: red-horizontal-angle&quot;&gt;Red (Horizontal Angle)&lt;&#x2F;a&gt;&lt;&#x2F;h4&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;2d-phong-illumination-in-webgl&#x2F;bump_map_red.png&quot; alt=&quot;bump_map_red.png&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;This channel encodes the horizontal angle of the surface normal.
A value of 0 denotes 0°, 64 (256&#x2F;4) denotes 90° (360°&#x2F;4) and so on.
This is why the right side of the red image is black - the horizontal angle of
the normal is 0°.&lt;&#x2F;p&gt;
&lt;h4 id=&quot;green-vertical-angle&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#green-vertical-angle&quot; aria-label=&quot;Anchor link for: green-vertical-angle&quot;&gt;Green (Vertical Angle)&lt;&#x2F;a&gt;&lt;&#x2F;h4&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;2d-phong-illumination-in-webgl&#x2F;bump_map_green.png&quot; alt=&quot;bump_map_green.png&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;This channel encodes the vertical angle of the surface normal.
Values are linearly interpolated between 0° and 90°.
0° indicates a vertical normal.
The middle and edges of the image are black because the surface normal is
straight up.&lt;&#x2F;p&gt;
&lt;h4 id=&quot;blue-depth&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#blue-depth&quot; aria-label=&quot;Anchor link for: blue-depth&quot;&gt;Blue (Depth)&lt;&#x2F;a&gt;&lt;&#x2F;h4&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;2d-phong-illumination-in-webgl&#x2F;bump_map_blue.png&quot; alt=&quot;bump_map_blue.png&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;The image above is slightly blue though it’s hard to see.
It represents the height in pixel-sized units of each pixel.
Heights of tiles range from 0 to 8 pixels, so the blue-est colour in that
picture is rgb(0, 0, 8) which looks almost black.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;light-map&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#light-map&quot; aria-label=&quot;Anchor link for: light-map&quot;&gt;Light Map&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;2d-phong-illumination-in-webgl&#x2F;light_map.png&quot; alt=&quot;light_map.png&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;This stores the ambient, diffuse and specular reflection coefficients in the
red, green and blue channels respectively.&lt;&#x2F;p&gt;
&lt;h4 id=&quot;red-ambient&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#red-ambient&quot; aria-label=&quot;Anchor link for: red-ambient&quot;&gt;Red (Ambient)&lt;&#x2F;a&gt;&lt;&#x2F;h4&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;2d-phong-illumination-in-webgl&#x2F;light_map_red.png&quot; alt=&quot;light_map_red.png&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;h4 id=&quot;green-diffuse&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#green-diffuse&quot; aria-label=&quot;Anchor link for: green-diffuse&quot;&gt;Green (Diffuse)&lt;&#x2F;a&gt;&lt;&#x2F;h4&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;2d-phong-illumination-in-webgl&#x2F;light_map_green.png&quot; alt=&quot;light_map_green.png&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;h4 id=&quot;blue-specular&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#blue-specular&quot; aria-label=&quot;Anchor link for: blue-specular&quot;&gt;Blue (Specular)&lt;&#x2F;a&gt;&lt;&#x2F;h4&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;2d-phong-illumination-in-webgl&#x2F;light_map_blue.png&quot; alt=&quot;light_map_blue.png&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;The grout between tiles isn’t very shiny, so it has a low specular reflection
coefficient&lt;&#x2F;p&gt;
&lt;h4 id=&quot;shine-map&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#shine-map&quot; aria-label=&quot;Anchor link for: shine-map&quot;&gt;Shine Map&lt;&#x2F;a&gt;&lt;&#x2F;h4&gt;
&lt;p&gt;This indicates how shiny each pixel is. It is used to determine the specular
exponent (the power to which the dot product is raised wen computing specular
lighting). Only one channel is used for this, and values can range from 0 to 255.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;2d-phong-illumination-in-webgl&#x2F;shine_map.png&quot; alt=&quot;shine_map.png&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Post Mortem of an Abandoned Game</title>
          <pubDate>Sun, 12 Jul 2015 22:46:01 +1000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/post-mortem-of-abandoned-game/</link>
          <guid>https://www.gridbugs.org/post-mortem-of-abandoned-game/</guid>
          <description xml:base="https://www.gridbugs.org/post-mortem-of-abandoned-game/">&lt;p&gt;I spent about 6 months of 2014 working in my spare time
on what I hoped would become a top-down
side-scrolling action-rpg. I’d just started playing Dark Souls,
and wanted to emulate its art style and combat, but in 2D.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;post-mortem-of-abandoned-game&#x2F;screenshot.png&quot; alt=&quot;screenshot.png&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;My aim from the start was to quickly implement features at a basic level, rather than getting bogged down in low-level details.
I implemented character animation, collision processing
and visible area detection over the course of several months. Often I would find myself starting to focus too much on one thing,
such as smooth interpolation between animation modes, or having characters slide along walls following a collision rather than
stopping abruptly. Nonetheless, I continued to make progress.&lt;&#x2F;p&gt;
&lt;p&gt;All the graphics were drawn using html canvas’s 2D drawing context. I was interested in comparing the relative performance of
the 2D drawing context and WebGL, which is native browser support for OpenGL ES.
I’d attempted to learn webgl on several occasions prior to this, but never had a project to apply it to until now.
I set about porting the low-level graphics functionality of the game to webgl, and unknowingly opened Pandora’s box.&lt;&#x2F;p&gt;
&lt;p&gt;Suddenly I had the power of shaders at my fingertips. I poured endless hours into writing shaders and meticulously crafting
bump maps and light maps for various scenes and marveling at the speed at which complex graphical effects could be applied.
(Shaders are programs which run on massively parallel hardware (GPUs), and perform computations on each vertex in a scene, and
each pixel on the screen.) I wrote a blur filter, a pixelate filter, a phong illumination system that used a collection of
special images to give the illusion of 3D textures.&lt;&#x2F;p&gt;
&lt;p&gt;At this point I started to lose sight of where the project was going. The cost of adding new content was increased by the shiny new graphics
engine, as images needed accompanying bump and light maps. I was starting to approach the limit of computation which can be done
in a single frame on my development machine (a 2013 macbook air). I started to doubt whether top-down was really the best viewing angle for
the task at hand, and wondered if &lt;a href=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;post-mortem-of-abandoned-game&#x2F;lttp.jpg&quot;&gt;3&#x2F;4 perspective&lt;&#x2F;a&gt; would be more appropriate, or if purely top-down implied
a more &lt;a href=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;post-mortem-of-abandoned-game&#x2F;teleglitch.jpg&quot;&gt;minimal art style&lt;&#x2F;a&gt;. I experimented with different styles of drawing but couldn’t settle
on anything I both liked and had the skill to create.&lt;&#x2F;p&gt;
&lt;p&gt;Eventually I decided I’d have better luck starting a new project from scratch. I learnt a lot about computer graphics and also about
how not to go about creating a game.&lt;&#x2F;p&gt;
&lt;p&gt;I decided to stop working on this project on a Friday night, and while liberating, it was also frustrating, so to prove
to myself that I could actually make games, I spent the weekend making
&lt;a href=&quot;https:&#x2F;&#x2F;games.gridbugs.org&#x2F;unfinished-game&#x2F;&quot;&gt;this little platform game&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;runnable-versions-of-game-engine-runs-in-browser&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#runnable-versions-of-game-engine-runs-in-browser&quot; aria-label=&quot;Anchor link for: runnable-versions-of-game-engine-runs-in-browser&quot;&gt;Runnable versions of game engine (runs in browser)&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;games.gridbugs.org&#x2F;abandoned-game-big&quot;&gt;Large area with buggy dynamic lighting and shaders&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;games.gridbugs.org&#x2F;abandoned-game-big-noshaders&quot;&gt;Large area with dynamic lighting but no shaders&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;games.gridbugs.org&#x2F;abandoned-game-small&quot;&gt;Small area with dynamic lighting but no shaders&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;shader-demos&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#shader-demos&quot; aria-label=&quot;Anchor link for: shader-demos&quot;&gt;Shader demos&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;games.gridbugs.org&#x2F;old-webgl-shader-demos&#x2F;pavement&#x2F;artwork&#x2F;shaders&#x2F;irregular_pavement&quot;&gt;Irregular pavement shader demo&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;games.gridbugs.org&#x2F;old-webgl-shader-demos&#x2F;tiles&#x2F;&quot;&gt;Tiles shader demo&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;games.gridbugs.org&#x2F;old-webgl-shader-demos&#x2F;waves&#x2F;&quot;&gt;Waves shader demo&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;gridbugs&#x2F;old-webgl-shader-demos&quot;&gt;source code&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
</description>
      </item>
      <item>
          <title>Top-down Sidescrolling Engine</title>
          <pubDate>Sat, 11 Jul 2015 22:46:01 +1000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/top-down-sidescrolling-engine/</link>
          <guid>https://www.gridbugs.org/top-down-sidescrolling-engine/</guid>
          <description xml:base="https://www.gridbugs.org/top-down-sidescrolling-engine/">&lt;p&gt;A partially-complete game engine I wrote in 2014 in javascript&#x2F;html5.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;a href=&quot;https:&#x2F;&#x2F;games.gridbugs.org&#x2F;top-down-sidescrolling-engine&quot;&gt;Demo (runs in browser)&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;top-down-sidescrolling-engine&#x2F;screenshot.png&quot; alt=&quot;screenshot.png&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Features:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;circle&#x2F;line-segment collision detection and processing&lt;&#x2F;li&gt;
&lt;li&gt;framework for animating top-down 2D characters based on a skeleton description and a collection of images&lt;&#x2F;li&gt;
&lt;li&gt;phong illumination in webgl&lt;&#x2F;li&gt;
&lt;li&gt;dynamic lighting and visible area detection&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
</description>
      </item>
      <item>
          <title>Pitch Controlled Game</title>
          <pubDate>Fri, 10 Jul 2015 22:46:01 +1000</pubDate>
          <author>Stephen Sherratt</author>
          <link>https://www.gridbugs.org/pitch-controlled-game/</link>
          <guid>https://www.gridbugs.org/pitch-controlled-game/</guid>
          <description xml:base="https://www.gridbugs.org/pitch-controlled-game/">&lt;p&gt;A game I made for UNSW ArtsWeek 2014. Control the cat by making noises of various pitches.
Collect the coins but avoid the ghosts.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;a href=&quot;https:&#x2F;&#x2F;games.gridbugs.org&#x2F;pitch-controlled-game&quot;&gt;Play in browser&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;www.gridbugs.org&#x2F;pitch-controlled-game&#x2F;screenshot.png&quot; alt=&quot;screenshot.png&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
</description>
      </item>
    </channel>
</rss>
