<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<link href="https://nega.tv/feed.xml" rel="self" type="application/atom+xml" />
<id>urn:uuid:019f19bc-a450-76fa-9e96-7e99760cf284</id>
<title>nega.tv Posts Feed</title>
<author><name>Josh Simmons</name></author>
<updated>2026-06-23T00:00:00Z</updated>
<entry>
<id>urn:uuid:019f1567-4bbb-7da8-a9c8-50e340afdf67</id>
<title>The Low-Tech AI Of Elden Ring</title>
<author><name>Josh Simmons</name></author>
<updated>2026-06-23T00:00:00Z</updated>
<link href="https://nega.tv/posts/low-tech-ai-of-elden-ring.html" rel="alternate" type="text/html" />
<content type="html">
&lt;p&gt;&lt;em&gt;FROMSOFT&lt;/em&gt; has a reputation for diverse and punishing npc encounters across the
entire Soulsborne extended series, but the implementation of the AI decision
making itself is perhaps unexpectedly low-tech. Since the majority of the code
is implemented in Havok Script (A games-oriented Lua implementation from Havok)
it’s pretty easy to take a peek behind the fog wall to see how they’re
implemented.&lt;/p&gt;&lt;p&gt;Note that none of what follows is original research, I’m just reading the code
that others have done the hard work of extracting, decompiling, and reversing.&lt;/p&gt;&lt;section id=&quot;goals&quot;&gt;&lt;h1&gt;Goals&lt;/h1&gt;&lt;p&gt;The primary tool of the &lt;em&gt;FROMSOFT&lt;/em&gt; AI approach is the Goal&lt;a id=&quot;fnref1&quot; href=&quot;#fn1&quot; role=&quot;doc-noteref&quot; style=&quot;--anchor-name: --fnref1&quot;&gt;&lt;sup&gt;1&lt;/sup&gt;&lt;/a&gt;, which is
their own terminology for a unique state that the AI can be in. Goals can be
parametized when instanciated, and can access data stored on the Actor itself,
but are otherwise really just an immutable table of functions.&lt;/p&gt;&lt;p&gt;Now the simplest option would be to organize states into a
&lt;a href=&quot;https://en.wikipedia.org/wiki/Finite-state_machine&quot;&gt;Finite State Machine&lt;/a&gt; or maybe a Hierarchical Finite State Machine, but
&lt;em&gt;FROMSOFT&lt;/em&gt; go one step further and give the system a stack of states. This turns
it from an &lt;a href=&quot;https://en.wikipedia.org/wiki/Finite-state_machine&quot;&gt;FSM&lt;/a&gt; into &lt;a href=&quot;https://en.wikipedia.org/wiki/Pushdown_automaton&quot;&gt;Pushdown Automaton (PDA)&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;That’s an entirely abstract definition, so after you get back from wikipedia
let’s talk about it concretely from the top down.&lt;/p&gt;&lt;p&gt;Each frame Actors will update the Goal on top of their stack of Goals. When the
Goal updates, it can then push more Goals as Sub-Goals onto the stack, the
topmost of which will execute next frame. The Goal’s update function returns a
value indicating either Continue, Success, or Failure. Continue will leave the
stack unchanged, the other two will cause the Goal to be popped from the stack.
Failure will additionally cause all other unexecuted Goals to be popped from the
stack up to the parent Goal (The Goal which pushed this sub-goal).&lt;/p&gt;&lt;p&gt;For example, we might define a Goal called &lt;code&gt;CoolBossBattle&lt;/code&gt;, during the course
of its execution it might then push a series of &lt;code&gt;Attack&lt;/code&gt; Sub-Goals. Those attack
Goals can be parametized by various means, but the main one is the animation id&lt;a id=&quot;fnref2&quot; href=&quot;#fn2&quot; role=&quot;doc-noteref&quot; style=&quot;--anchor-name: --fnref2&quot;&gt;&lt;sup&gt;2&lt;/sup&gt;&lt;/a&gt;.&lt;/p&gt;&lt;pre&gt;&lt;code&gt;[ GOAL STACK ]

3: Attack (R2, Combo)           &amp;lt;&amp;lt;&amp;lt;&amp;lt;-- Currently Updating
2: Attack (R2, Repeat)
1: Attack (R2, Finisher)
0: CoolBossBattle
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;After a few seconds the first attack lands, and that Goal completes with
success and is popped from the stack. However the next fails, causing the stack
to unwind to its parent.&lt;/p&gt;&lt;pre&gt;&lt;code&gt;[ GOAL STACK ]

2: Attack (R2, Repeat)          &amp;lt;&amp;lt;&amp;lt;&amp;lt;-- Failed, will be popped from the stack.
1: Attack (R2, Finisher)        &amp;lt;&amp;lt;&amp;lt;&amp;lt;-- Will be removed as well.
0: CoolBossBattle
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Readying it to chose its next action now that the attempted combo of attacks has
ended.&lt;/p&gt;&lt;pre&gt;&lt;code&gt;[ GOAL STACK ]

2: Attack(L1)
1: Attack(L1)
0: CoolBossBattle               &amp;lt;&amp;lt;&amp;lt;&amp;lt;-- Updating, pushes 1, and 2 for the next frame.
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Not too complex&lt;a id=&quot;fnref3&quot; href=&quot;#fn3&quot; role=&quot;doc-noteref&quot; style=&quot;--anchor-name: --fnref3&quot;&gt;&lt;sup&gt;3&lt;/sup&gt;&lt;/a&gt;!&lt;/p&gt;&lt;p&gt;In their APIs they refer to the root of this stack as the “Top Level Goal”, which
I’ve made confusing by referring to the currently executing goal as the “top” of
the stack. So keep in mind those are separate things.&lt;/p&gt;&lt;/section&gt;&lt;section id=&quot;activate&quot;&gt;&lt;h1&gt;Activate&lt;/h1&gt;&lt;p&gt;Goals are defined by a few functions used as callbacks, and the one which
contains the most AI logic is usually activate. This is called the first time
that a Goal is updated, and then every subsequent time that the Goal exhausts
its Sub-Goals and starts executing again.&lt;/p&gt;&lt;p&gt;For boss and regular npc Goals the code in Activate is responsible for choosing
the next action that the Actor will take using a mix of context from the world
and Actor, and randomness (which also comes from the Actor itself).&lt;/p&gt;&lt;p&gt;The most widely used approach uses common code to perform a weighted random
selection between a number of Actions (which are just functions), calling the
winner.&lt;/p&gt;&lt;p&gt;To return to our &lt;code&gt;CoolBossBattle&lt;/code&gt;, this time in some Rusty pseudocode…&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-rust&quot;&gt;&lt;span data-hl=&quot;keyword&quot;&gt;fn&lt;/span&gt; &lt;span data-hl=&quot;function&quot;&gt;action_giga_death_ray&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;(&lt;/span&gt;&lt;span data-hl=&quot;variable.parameter&quot;&gt;goals&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;:&lt;/span&gt; &lt;span data-hl=&quot;operator&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span data-hl=&quot;type&quot;&gt;Goals&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;,&lt;/span&gt; &lt;span data-hl=&quot;variable.parameter&quot;&gt;actor&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;:&lt;/span&gt; &lt;span data-hl=&quot;operator&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span data-hl=&quot;type&quot;&gt;Actor&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;)&lt;/span&gt; &lt;span data-hl=&quot;punctuation.bracket&quot;&gt;{&lt;/span&gt;
    &lt;span data-hl=&quot;function.macro&quot;&gt;todo&lt;/span&gt;&lt;span data-hl=&quot;function.macro&quot;&gt;!&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;(&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;)&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;;&lt;/span&gt;
&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;}&lt;/span&gt;

&lt;span data-hl=&quot;keyword&quot;&gt;fn&lt;/span&gt; &lt;span data-hl=&quot;function&quot;&gt;action_leap_attack&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;(&lt;/span&gt;&lt;span data-hl=&quot;variable.parameter&quot;&gt;goals&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;:&lt;/span&gt; &lt;span data-hl=&quot;operator&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span data-hl=&quot;type&quot;&gt;Goals&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;,&lt;/span&gt; &lt;span data-hl=&quot;variable.parameter&quot;&gt;actor&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;:&lt;/span&gt; &lt;span data-hl=&quot;operator&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span data-hl=&quot;type&quot;&gt;Actor&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;)&lt;/span&gt; &lt;span data-hl=&quot;punctuation.bracket&quot;&gt;{&lt;/span&gt;
    &lt;span data-hl=&quot;function.macro&quot;&gt;todo&lt;/span&gt;&lt;span data-hl=&quot;function.macro&quot;&gt;!&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;(&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;)&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;;&lt;/span&gt;
&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;}&lt;/span&gt;

&lt;span data-hl=&quot;keyword&quot;&gt;fn&lt;/span&gt; &lt;span data-hl=&quot;function&quot;&gt;action_ground_slam&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;(&lt;/span&gt;&lt;span data-hl=&quot;variable.parameter&quot;&gt;goals&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;:&lt;/span&gt; &lt;span data-hl=&quot;operator&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span data-hl=&quot;type&quot;&gt;Goals&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;,&lt;/span&gt; &lt;span data-hl=&quot;variable.parameter&quot;&gt;actor&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;:&lt;/span&gt; &lt;span data-hl=&quot;operator&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span data-hl=&quot;type&quot;&gt;Actor&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;)&lt;/span&gt; &lt;span data-hl=&quot;punctuation.bracket&quot;&gt;{&lt;/span&gt;
    &lt;span data-hl=&quot;function.macro&quot;&gt;todo&lt;/span&gt;&lt;span data-hl=&quot;function.macro&quot;&gt;!&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;(&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;)&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;;&lt;/span&gt;
&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;}&lt;/span&gt;

&lt;span data-hl=&quot;keyword&quot;&gt;fn&lt;/span&gt; &lt;span data-hl=&quot;function&quot;&gt;action_light_attack_combo&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;(&lt;/span&gt;&lt;span data-hl=&quot;variable.parameter&quot;&gt;goals&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;:&lt;/span&gt; &lt;span data-hl=&quot;operator&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span data-hl=&quot;type&quot;&gt;Goals&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;,&lt;/span&gt; &lt;span data-hl=&quot;variable.parameter&quot;&gt;actor&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;:&lt;/span&gt; &lt;span data-hl=&quot;operator&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span data-hl=&quot;type&quot;&gt;Actor&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;)&lt;/span&gt; &lt;span data-hl=&quot;punctuation.bracket&quot;&gt;{&lt;/span&gt;
    &lt;span data-hl=&quot;keyword&quot;&gt;let&lt;/span&gt; target_distance = actor&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;.&lt;/span&gt;&lt;span data-hl=&quot;function.method&quot;&gt;target_distance&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;(&lt;/span&gt;&lt;span data-hl=&quot;type&quot;&gt;Target&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;::&lt;/span&gt;&lt;span data-hl=&quot;constructor&quot;&gt;Enemy&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;)&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;;&lt;/span&gt;
    &lt;span data-hl=&quot;keyword&quot;&gt;let&lt;/span&gt; fate = actor&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;.&lt;/span&gt;&lt;span data-hl=&quot;function.method&quot;&gt;next_random&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;(&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;)&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;;&lt;/span&gt;

    &lt;span data-hl=&quot;comment&quot;&gt;// ApproachTarget itself being a goal defined in common code!&lt;/span&gt;
    &lt;span data-hl=&quot;keyword&quot;&gt;if&lt;/span&gt; target_distance &amp;gt; &lt;span data-hl=&quot;constant.builtin&quot;&gt;2.0&lt;/span&gt; &lt;span data-hl=&quot;punctuation.bracket&quot;&gt;{&lt;/span&gt;
        goals&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;.&lt;/span&gt;&lt;span data-hl=&quot;function.method&quot;&gt;push_sub_goal&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;(&lt;/span&gt;&lt;span data-hl=&quot;type&quot;&gt;Goal&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;::&lt;/span&gt;&lt;span data-hl=&quot;constructor&quot;&gt;ApproachTarget&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;,&lt;/span&gt; &lt;span data-hl=&quot;type&quot;&gt;Target&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;::&lt;/span&gt;&lt;span data-hl=&quot;constructor&quot;&gt;Enemy&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;)&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;;&lt;/span&gt;
    &lt;span data-hl=&quot;punctuation.bracket&quot;&gt;}&lt;/span&gt;

    goals&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;.&lt;/span&gt;&lt;span data-hl=&quot;function.method&quot;&gt;push_sub_goal&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;(&lt;/span&gt;&lt;span data-hl=&quot;type&quot;&gt;Goal&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;::&lt;/span&gt;&lt;span data-hl=&quot;constructor&quot;&gt;Attack&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;,&lt;/span&gt; &lt;span data-hl=&quot;type&quot;&gt;AnimId&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;::&lt;/span&gt;&lt;span data-hl=&quot;constructor&quot;&gt;R1&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;,&lt;/span&gt; &lt;span data-hl=&quot;type&quot;&gt;Combo&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;::&lt;/span&gt;&lt;span data-hl=&quot;constructor&quot;&gt;Initial&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;)&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;;&lt;/span&gt;
    goals&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;.&lt;/span&gt;&lt;span data-hl=&quot;function.method&quot;&gt;push_sub_goal&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;(&lt;/span&gt;&lt;span data-hl=&quot;type&quot;&gt;Goal&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;::&lt;/span&gt;&lt;span data-hl=&quot;constructor&quot;&gt;Attack&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;,&lt;/span&gt; &lt;span data-hl=&quot;type&quot;&gt;AnimId&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;::&lt;/span&gt;&lt;span data-hl=&quot;constructor&quot;&gt;R1&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;,&lt;/span&gt; &lt;span data-hl=&quot;type&quot;&gt;Combo&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;::&lt;/span&gt;&lt;span data-hl=&quot;constructor&quot;&gt;Repeat&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;)&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;;&lt;/span&gt;

    &lt;span data-hl=&quot;comment&quot;&gt;// Unlucky buster! It&#39;s the long combo.&lt;/span&gt;
    &lt;span data-hl=&quot;keyword&quot;&gt;if&lt;/span&gt; fate &amp;lt; &lt;span data-hl=&quot;constant.builtin&quot;&gt;0.2&lt;/span&gt; &lt;span data-hl=&quot;punctuation.bracket&quot;&gt;{&lt;/span&gt;
        goals&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;.&lt;/span&gt;&lt;span data-hl=&quot;function.method&quot;&gt;push_sub_goal&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;(&lt;/span&gt;&lt;span data-hl=&quot;type&quot;&gt;Goal&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;::&lt;/span&gt;&lt;span data-hl=&quot;constructor&quot;&gt;Attack&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;,&lt;/span&gt; &lt;span data-hl=&quot;type&quot;&gt;AnimId&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;::&lt;/span&gt;&lt;span data-hl=&quot;constructor&quot;&gt;R1&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;,&lt;/span&gt; &lt;span data-hl=&quot;type&quot;&gt;Combo&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;::&lt;/span&gt;&lt;span data-hl=&quot;constructor&quot;&gt;Repeat&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;)&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;;&lt;/span&gt;
    &lt;span data-hl=&quot;punctuation.bracket&quot;&gt;}&lt;/span&gt;

    goals&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;.&lt;/span&gt;&lt;span data-hl=&quot;function.method&quot;&gt;push_sub_goal&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;(&lt;/span&gt;&lt;span data-hl=&quot;type&quot;&gt;Goal&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;::&lt;/span&gt;&lt;span data-hl=&quot;constructor&quot;&gt;Attack&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;,&lt;/span&gt; &lt;span data-hl=&quot;type&quot;&gt;AnimId&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;::&lt;/span&gt;&lt;span data-hl=&quot;constructor&quot;&gt;R1&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;,&lt;/span&gt; &lt;span data-hl=&quot;type&quot;&gt;Combo&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;::&lt;/span&gt;&lt;span data-hl=&quot;constructor&quot;&gt;Finisher&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;)&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;;&lt;/span&gt;
&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;}&lt;/span&gt;

&lt;span data-hl=&quot;keyword&quot;&gt;fn&lt;/span&gt; &lt;span data-hl=&quot;function&quot;&gt;action_heavy_attack_combo&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;(&lt;/span&gt;&lt;span data-hl=&quot;variable.parameter&quot;&gt;goals&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;:&lt;/span&gt; &lt;span data-hl=&quot;operator&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span data-hl=&quot;type&quot;&gt;Goals&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;,&lt;/span&gt; &lt;span data-hl=&quot;variable.parameter&quot;&gt;actor&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;:&lt;/span&gt; &lt;span data-hl=&quot;operator&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span data-hl=&quot;type&quot;&gt;Actor&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;)&lt;/span&gt; &lt;span data-hl=&quot;punctuation.bracket&quot;&gt;{&lt;/span&gt;
    &lt;span data-hl=&quot;function.macro&quot;&gt;todo&lt;/span&gt;&lt;span data-hl=&quot;function.macro&quot;&gt;!&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;(&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;)&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;;&lt;/span&gt;
&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;}&lt;/span&gt;

&lt;span data-hl=&quot;keyword&quot;&gt;fn&lt;/span&gt; &lt;span data-hl=&quot;function&quot;&gt;activate&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;(&lt;/span&gt;&lt;span data-hl=&quot;operator&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span data-hl=&quot;variable.builtin&quot;&gt;self&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;,&lt;/span&gt; &lt;span data-hl=&quot;variable.parameter&quot;&gt;goals&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;:&lt;/span&gt; &lt;span data-hl=&quot;operator&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span data-hl=&quot;type&quot;&gt;Goals&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;,&lt;/span&gt; &lt;span data-hl=&quot;variable.parameter&quot;&gt;actor&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;:&lt;/span&gt; &lt;span data-hl=&quot;operator&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span data-hl=&quot;type&quot;&gt;Actor&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;)&lt;/span&gt; &lt;span data-hl=&quot;punctuation.bracket&quot;&gt;{&lt;/span&gt;
    &lt;span data-hl=&quot;keyword&quot;&gt;let&lt;/span&gt; target_distance = actor&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;.&lt;/span&gt;&lt;span data-hl=&quot;function.method&quot;&gt;target_distance&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;(&lt;/span&gt;&lt;span data-hl=&quot;type&quot;&gt;Target&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;::&lt;/span&gt;&lt;span data-hl=&quot;constructor&quot;&gt;Enemy&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;)&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;;&lt;/span&gt;

    &lt;span data-hl=&quot;keyword&quot;&gt;let&lt;/span&gt; &lt;span data-hl=&quot;keyword&quot;&gt;mut&lt;/span&gt; weights = &lt;span data-hl=&quot;keyword&quot;&gt;if&lt;/span&gt; target_distance &amp;gt; &lt;span data-hl=&quot;constant.builtin&quot;&gt;6.0&lt;/span&gt; &lt;span data-hl=&quot;punctuation.bracket&quot;&gt;{&lt;/span&gt;
        &lt;span data-hl=&quot;punctuation.bracket&quot;&gt;[&lt;/span&gt;
            &lt;span data-hl=&quot;constant.builtin&quot;&gt;15.0&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;,&lt;/span&gt;
            &lt;span data-hl=&quot;constant.builtin&quot;&gt;65.0&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;,&lt;/span&gt;
            &lt;span data-hl=&quot;constant.builtin&quot;&gt;0.0&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;,&lt;/span&gt;
            &lt;span data-hl=&quot;constant.builtin&quot;&gt;10.0&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;,&lt;/span&gt;
            &lt;span data-hl=&quot;constant.builtin&quot;&gt;10.0&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;,&lt;/span&gt;
        &lt;span data-hl=&quot;punctuation.bracket&quot;&gt;]&lt;/span&gt;
    &lt;span data-hl=&quot;punctuation.bracket&quot;&gt;}&lt;/span&gt; &lt;span data-hl=&quot;keyword&quot;&gt;else&lt;/span&gt; &lt;span data-hl=&quot;keyword&quot;&gt;if&lt;/span&gt; target_distance &amp;gt; &lt;span data-hl=&quot;constant.builtin&quot;&gt;1.5&lt;/span&gt; &lt;span data-hl=&quot;punctuation.bracket&quot;&gt;{&lt;/span&gt;
        &lt;span data-hl=&quot;punctuation.bracket&quot;&gt;[&lt;/span&gt;
            &lt;span data-hl=&quot;constant.builtin&quot;&gt;0.0&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;,&lt;/span&gt;
            &lt;span data-hl=&quot;constant.builtin&quot;&gt;0.0&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;,&lt;/span&gt;
            &lt;span data-hl=&quot;constant.builtin&quot;&gt;5.0&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;,&lt;/span&gt;
            &lt;span data-hl=&quot;constant.builtin&quot;&gt;60.0&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;,&lt;/span&gt;
            &lt;span data-hl=&quot;constant.builtin&quot;&gt;35.0&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;,&lt;/span&gt;
        &lt;span data-hl=&quot;punctuation.bracket&quot;&gt;]&lt;/span&gt;
    &lt;span data-hl=&quot;punctuation.bracket&quot;&gt;}&lt;/span&gt; &lt;span data-hl=&quot;keyword&quot;&gt;else&lt;/span&gt; &lt;span data-hl=&quot;punctuation.bracket&quot;&gt;{&lt;/span&gt;
        &lt;span data-hl=&quot;punctuation.bracket&quot;&gt;[&lt;/span&gt;
            &lt;span data-hl=&quot;constant.builtin&quot;&gt;0.0&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;,&lt;/span&gt;
            &lt;span data-hl=&quot;constant.builtin&quot;&gt;0.0&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;,&lt;/span&gt;
            &lt;span data-hl=&quot;constant.builtin&quot;&gt;20.0&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;,&lt;/span&gt;
            &lt;span data-hl=&quot;constant.builtin&quot;&gt;40.0&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;,&lt;/span&gt;
            &lt;span data-hl=&quot;constant.builtin&quot;&gt;40.0&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;,&lt;/span&gt;
        &lt;span data-hl=&quot;punctuation.bracket&quot;&gt;]&lt;/span&gt;
    &lt;span data-hl=&quot;punctuation.bracket&quot;&gt;}&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;;&lt;/span&gt;

    &lt;span data-hl=&quot;comment&quot;&gt;// This doesn&#39;t exactly work this way in the Lua code, and these cooldowns&lt;/span&gt;
    &lt;span data-hl=&quot;comment&quot;&gt;// don&#39;t make sense either, but hopefully it gives the rough idea.&lt;/span&gt;
    &lt;span data-hl=&quot;comment&quot;&gt;//&lt;/span&gt;
    &lt;span data-hl=&quot;comment&quot;&gt;// The helper function is checking last played data for the animation on the&lt;/span&gt;
    &lt;span data-hl=&quot;comment&quot;&gt;// Actor itself, and then modifying the weights before they go into the&lt;/span&gt;
    &lt;span data-hl=&quot;comment&quot;&gt;// common battle randomized selection.&lt;/span&gt;
    weights&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;[&lt;/span&gt;&lt;span data-hl=&quot;constant.builtin&quot;&gt;3&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;]&lt;/span&gt; = &lt;span data-hl=&quot;keyword&quot;&gt;if&lt;/span&gt; common&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;::&lt;/span&gt;&lt;span data-hl=&quot;function&quot;&gt;is_cooldown&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;(&lt;/span&gt;goals&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;,&lt;/span&gt; actor&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;,&lt;/span&gt; &lt;span data-hl=&quot;type&quot;&gt;AnimId&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;::&lt;/span&gt;&lt;span data-hl=&quot;constructor&quot;&gt;R1&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;,&lt;/span&gt; &lt;span data-hl=&quot;constant.builtin&quot;&gt;8.0&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;)&lt;/span&gt; &lt;span data-hl=&quot;punctuation.bracket&quot;&gt;{&lt;/span&gt; &lt;span data-hl=&quot;constant.builtin&quot;&gt;0.0&lt;/span&gt; &lt;span data-hl=&quot;punctuation.bracket&quot;&gt;}&lt;/span&gt; &lt;span data-hl=&quot;keyword&quot;&gt;else&lt;/span&gt; &lt;span data-hl=&quot;punctuation.bracket&quot;&gt;{&lt;/span&gt; weights&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;[&lt;/span&gt;&lt;span data-hl=&quot;constant.builtin&quot;&gt;3&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;]&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;;&lt;/span&gt; &lt;span data-hl=&quot;punctuation.bracket&quot;&gt;}&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;;&lt;/span&gt;
    weights&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;[&lt;/span&gt;&lt;span data-hl=&quot;constant.builtin&quot;&gt;4&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;]&lt;/span&gt; = &lt;span data-hl=&quot;keyword&quot;&gt;if&lt;/span&gt; common&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;::&lt;/span&gt;&lt;span data-hl=&quot;function&quot;&gt;is_cooldown&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;(&lt;/span&gt;goals&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;,&lt;/span&gt; actor&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;,&lt;/span&gt; &lt;span data-hl=&quot;type&quot;&gt;AnimId&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;::&lt;/span&gt;&lt;span data-hl=&quot;constructor&quot;&gt;R2&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;,&lt;/span&gt; &lt;span data-hl=&quot;constant.builtin&quot;&gt;10.0&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;)&lt;/span&gt; &lt;span data-hl=&quot;punctuation.bracket&quot;&gt;{&lt;/span&gt; &lt;span data-hl=&quot;constant.builtin&quot;&gt;0.0&lt;/span&gt; &lt;span data-hl=&quot;punctuation.bracket&quot;&gt;}&lt;/span&gt; &lt;span data-hl=&quot;keyword&quot;&gt;else&lt;/span&gt; &lt;span data-hl=&quot;punctuation.bracket&quot;&gt;{&lt;/span&gt; weights&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;[&lt;/span&gt;&lt;span data-hl=&quot;constant.builtin&quot;&gt;4&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;]&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;;&lt;/span&gt; &lt;span data-hl=&quot;punctuation.bracket&quot;&gt;}&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;;&lt;/span&gt;

    &lt;span data-hl=&quot;keyword&quot;&gt;let&lt;/span&gt; actions = &lt;span data-hl=&quot;punctuation.bracket&quot;&gt;[&lt;/span&gt;
        action_giga_death_ray&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;,&lt;/span&gt;
        action_leap_attack&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;,&lt;/span&gt;
        action_ground_slam&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;,&lt;/span&gt;
        action_light_attack_combo&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;,&lt;/span&gt;
        action_heavy_attack_combo&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;,&lt;/span&gt;
    &lt;span data-hl=&quot;punctuation.bracket&quot;&gt;]&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;;&lt;/span&gt;

    &lt;span data-hl=&quot;comment&quot;&gt;// Does some common setup for the number of actions and then rolls the dice&lt;/span&gt;
    &lt;span data-hl=&quot;comment&quot;&gt;// and chooses which function to call.&lt;/span&gt;
    common&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;::&lt;/span&gt;&lt;span data-hl=&quot;function&quot;&gt;battle_activate&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;(&lt;/span&gt;goals&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;,&lt;/span&gt; actor&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;,&lt;/span&gt; weights&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;,&lt;/span&gt; actions&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;)&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;;&lt;/span&gt;
&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Modifying the weights dynamically is handled in many different ways, but the
most common are simple rng rolls from the actor and hp thresholding.&lt;/p&gt;&lt;p&gt;Other, simpler, Goals than the top level battle Goal for an Actor may simply
push a few sub-goals, perhaps reading some data from the Goal parameters. The
nesting means that it’s possible to compose quite complex behavior from simple
building blocks.&lt;/p&gt;&lt;/section&gt;&lt;section id=&quot;interrupts&quot;&gt;&lt;h1&gt;Interrupts&lt;/h1&gt;&lt;p&gt;The other major callback defined for goals is the Interrupt. As the name
suggests, this allows Goals to respond immediately to external events which are
mostly configured on the Actor itself.&lt;/p&gt;&lt;p&gt;My understanding is that interrupts bubble up, that is, it will run the
interrupt on the currently executing Goal and then its parents recursively,
until it runs out of Goals or one of the interrupt callbacks returns true to
indicate it has consumed the interrupt.&lt;/p&gt;&lt;p&gt;For example, if I wanted CoolBoss to move into a furious rage of attacks as soon
as I set it on fire, then I might implement something like the following.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-rust&quot;&gt;&lt;span data-hl=&quot;keyword&quot;&gt;fn&lt;/span&gt; &lt;span data-hl=&quot;function&quot;&gt;interrupt&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;(&lt;/span&gt;&lt;span data-hl=&quot;operator&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span data-hl=&quot;variable.builtin&quot;&gt;self&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;,&lt;/span&gt; &lt;span data-hl=&quot;variable.parameter&quot;&gt;goals&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;:&lt;/span&gt; &lt;span data-hl=&quot;operator&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span data-hl=&quot;type&quot;&gt;Goals&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;,&lt;/span&gt; &lt;span data-hl=&quot;variable.parameter&quot;&gt;actor&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;:&lt;/span&gt; &lt;span data-hl=&quot;operator&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span data-hl=&quot;type&quot;&gt;Actor&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;,&lt;/span&gt; &lt;span data-hl=&quot;variable.parameter&quot;&gt;interrupt&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;:&lt;/span&gt; &lt;span data-hl=&quot;type&quot;&gt;Interrupt&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;)&lt;/span&gt; &lt;span data-hl=&quot;punctuation.bracket&quot;&gt;{&lt;/span&gt;
    &lt;span data-hl=&quot;keyword&quot;&gt;match&lt;/span&gt; interrupt &lt;span data-hl=&quot;punctuation.bracket&quot;&gt;{&lt;/span&gt;
        &lt;span data-hl=&quot;comment&quot;&gt;// If I start burning, attack!&lt;/span&gt;
        &lt;span data-hl=&quot;type&quot;&gt;SpecialEffectActivate&lt;/span&gt; &lt;span data-hl=&quot;punctuation.bracket&quot;&gt;{&lt;/span&gt;
            target&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;,&lt;/span&gt;
            special_effect&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;,&lt;/span&gt;
        &lt;span data-hl=&quot;punctuation.bracket&quot;&gt;}&lt;/span&gt; =&amp;gt; &lt;span data-hl=&quot;punctuation.bracket&quot;&gt;{&lt;/span&gt;
            &lt;span data-hl=&quot;keyword&quot;&gt;if&lt;/span&gt; target == &lt;span data-hl=&quot;type&quot;&gt;Target&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;::&lt;/span&gt;&lt;span data-hl=&quot;constructor&quot;&gt;Self&lt;/span&gt; &amp;amp;&amp;amp; special_effect == &lt;span data-hl=&quot;type&quot;&gt;SpecialEffect&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;::&lt;/span&gt;&lt;span data-hl=&quot;constructor&quot;&gt;Fire&lt;/span&gt; &lt;span data-hl=&quot;punctuation.bracket&quot;&gt;{&lt;/span&gt;
                &lt;span data-hl=&quot;comment&quot;&gt;// Since there might still be other things running when&lt;/span&gt;
                &lt;span data-hl=&quot;comment&quot;&gt;// interrupt is called we need to unwind so we&#39;re on top again.&lt;/span&gt;
                goals&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;.&lt;/span&gt;&lt;span data-hl=&quot;function.method&quot;&gt;clear_sub_goals&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;(&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;)&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;;&lt;/span&gt;

                goals&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;.&lt;/span&gt;&lt;span data-hl=&quot;function.method&quot;&gt;push_sub_goal&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;(&lt;/span&gt;&lt;span data-hl=&quot;type&quot;&gt;Goal&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;::&lt;/span&gt;&lt;span data-hl=&quot;constructor&quot;&gt;Attack&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;,&lt;/span&gt; &lt;span data-hl=&quot;type&quot;&gt;AnimId&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;::&lt;/span&gt;&lt;span data-hl=&quot;constructor&quot;&gt;R1&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;)&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;;&lt;/span&gt;
                goals&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;.&lt;/span&gt;&lt;span data-hl=&quot;function.method&quot;&gt;push_sub_goal&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;(&lt;/span&gt;&lt;span data-hl=&quot;type&quot;&gt;Goal&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;::&lt;/span&gt;&lt;span data-hl=&quot;constructor&quot;&gt;Attack&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;,&lt;/span&gt; &lt;span data-hl=&quot;type&quot;&gt;AnimId&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;::&lt;/span&gt;&lt;span data-hl=&quot;constructor&quot;&gt;R2&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;)&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;;&lt;/span&gt;
                goals&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;.&lt;/span&gt;&lt;span data-hl=&quot;function.method&quot;&gt;push_sub_goal&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;(&lt;/span&gt;&lt;span data-hl=&quot;type&quot;&gt;Goal&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;::&lt;/span&gt;&lt;span data-hl=&quot;constructor&quot;&gt;Attack&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;,&lt;/span&gt; &lt;span data-hl=&quot;type&quot;&gt;AnimId&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;::&lt;/span&gt;&lt;span data-hl=&quot;constructor&quot;&gt;R1&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;)&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;;&lt;/span&gt;
                goals&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;.&lt;/span&gt;&lt;span data-hl=&quot;function.method&quot;&gt;push_sub_goal&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;(&lt;/span&gt;&lt;span data-hl=&quot;type&quot;&gt;Goal&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;::&lt;/span&gt;&lt;span data-hl=&quot;constructor&quot;&gt;Attack&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;,&lt;/span&gt; &lt;span data-hl=&quot;type&quot;&gt;AnimId&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;::&lt;/span&gt;&lt;span data-hl=&quot;constructor&quot;&gt;R2&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;)&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;;&lt;/span&gt;

                &lt;span data-hl=&quot;keyword&quot;&gt;return&lt;/span&gt; &lt;span data-hl=&quot;constant.builtin&quot;&gt;true&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;;&lt;/span&gt;
            &lt;span data-hl=&quot;punctuation.bracket&quot;&gt;}&lt;/span&gt;
        &lt;span data-hl=&quot;punctuation.bracket&quot;&gt;}&lt;/span&gt;
        &lt;span data-hl=&quot;comment&quot;&gt;// If somebody uses an item they might be in for it.&lt;/span&gt;
        &lt;span data-hl=&quot;constructor&quot;&gt;UseItem&lt;/span&gt; =&amp;gt; &lt;span data-hl=&quot;punctuation.bracket&quot;&gt;{&lt;/span&gt;
            &lt;span data-hl=&quot;keyword&quot;&gt;let&lt;/span&gt; fate = actor&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;.&lt;/span&gt;&lt;span data-hl=&quot;function.method&quot;&gt;next_random&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;(&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;)&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;;&lt;/span&gt;
            &lt;span data-hl=&quot;keyword&quot;&gt;if&lt;/span&gt; fate &amp;lt; &lt;span data-hl=&quot;constant.builtin&quot;&gt;0.5&lt;/span&gt; &lt;span data-hl=&quot;punctuation.bracket&quot;&gt;{&lt;/span&gt;
                goals&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;.&lt;/span&gt;&lt;span data-hl=&quot;function.method&quot;&gt;clear_sub_goals&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;(&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;)&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;;&lt;/span&gt;
                &lt;span data-hl=&quot;function&quot;&gt;action_light_attack_combo&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;(&lt;/span&gt;goals&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;,&lt;/span&gt; actor&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;)&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;;&lt;/span&gt;
            &lt;span data-hl=&quot;punctuation.bracket&quot;&gt;}&lt;/span&gt;
        &lt;span data-hl=&quot;punctuation.bracket&quot;&gt;}&lt;/span&gt;
        &lt;span data-hl=&quot;comment&quot;&gt;// Perform a ground slam if I get attacked from underneath.&lt;/span&gt;
        &lt;span data-hl=&quot;type&quot;&gt;Damage&lt;/span&gt; &lt;span data-hl=&quot;punctuation.bracket&quot;&gt;{&lt;/span&gt;
            target&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;,&lt;/span&gt;
        &lt;span data-hl=&quot;punctuation.bracket&quot;&gt;}&lt;/span&gt; =&amp;gt; &lt;span data-hl=&quot;punctuation.bracket&quot;&gt;{&lt;/span&gt;
            &lt;span data-hl=&quot;keyword&quot;&gt;if&lt;/span&gt; target == &lt;span data-hl=&quot;type&quot;&gt;Target&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;::&lt;/span&gt;&lt;span data-hl=&quot;constructor&quot;&gt;Self&lt;/span&gt; &lt;span data-hl=&quot;punctuation.bracket&quot;&gt;{&lt;/span&gt;
                &lt;span data-hl=&quot;keyword&quot;&gt;let&lt;/span&gt; distance = actor&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;.&lt;/span&gt;&lt;span data-hl=&quot;function.method&quot;&gt;target_distance&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;(&lt;/span&gt;&lt;span data-hl=&quot;type&quot;&gt;Target&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;::&lt;/span&gt;&lt;span data-hl=&quot;constructor&quot;&gt;Enemy&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;)&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;;&lt;/span&gt;
                &lt;span data-hl=&quot;keyword&quot;&gt;let&lt;/span&gt; fate = actor&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;.&lt;/span&gt;&lt;span data-hl=&quot;function.method&quot;&gt;next_random&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;(&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;)&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;;&lt;/span&gt;
                &lt;span data-hl=&quot;keyword&quot;&gt;if&lt;/span&gt; distance &amp;lt; &lt;span data-hl=&quot;constant.builtin&quot;&gt;1.0&lt;/span&gt; &amp;amp;&amp;amp; fate &amp;lt; &lt;span data-hl=&quot;constant.builtin&quot;&gt;0.8&lt;/span&gt; &lt;span data-hl=&quot;punctuation.bracket&quot;&gt;{&lt;/span&gt;
                    goals&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;.&lt;/span&gt;&lt;span data-hl=&quot;function.method&quot;&gt;clear_sub_goals&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;(&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;)&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;;&lt;/span&gt;
                    &lt;span data-hl=&quot;function&quot;&gt;action_ground_slam&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;(&lt;/span&gt;goals&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;,&lt;/span&gt; actor&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;)&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;;&lt;/span&gt;
                &lt;span data-hl=&quot;punctuation.bracket&quot;&gt;}&lt;/span&gt;
            &lt;span data-hl=&quot;punctuation.bracket&quot;&gt;}&lt;/span&gt;
        &lt;span data-hl=&quot;punctuation.bracket&quot;&gt;}&lt;/span&gt;
        _ =&amp;gt; &lt;span data-hl=&quot;punctuation.bracket&quot;&gt;{&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;}&lt;/span&gt;
    &lt;span data-hl=&quot;punctuation.bracket&quot;&gt;}&lt;/span&gt;

    &lt;span data-hl=&quot;constant.builtin&quot;&gt;false&lt;/span&gt;
&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This is used to implement some truly evil features, for example the Bell Bearing
Hunter will detect you spell casting or using an item and from there has an 85%
chance to immediately abort its current action and launch into an attack.&lt;/p&gt;&lt;p&gt;They also make use of dynamic spatial watch regions configured on Actors, which
trigger interrupts. For example you might add a watch for the area behind or
under a boss, and use that to adapt their behavior immediately when the player
tries to get clever.&lt;/p&gt;&lt;/section&gt;&lt;section id=&quot;timeouts&quot;&gt;&lt;h1&gt;Timeouts&lt;/h1&gt;&lt;p&gt;Goals, in addition to their individual state, carry a lifetime value in seconds.
This is used to break out of states which become stuck for whatever reason, and
lifetime seems to be used mostly as a bug containment mechanism.&lt;/p&gt;&lt;p&gt;It’s also possible to modify the lifetime of a parent goal during execution, to
indicate continued forward progress.&lt;/p&gt;&lt;/section&gt;&lt;section id=&quot;actor-data-access&quot;&gt;&lt;h1&gt;Actor Data Access&lt;/h1&gt;&lt;p&gt;In many AI decision systems you might have heard of fancy systems for data
storage like “blackboards”. In the Souls games there’s an array of floats on
each Actor which are set and read arbitraily from Goals by index. Good enough I
suppose!&lt;/p&gt;&lt;p&gt;A callback I didn’t mention before, Initialise, is commonly used to reset this
data when an Actor is assigned a new Top Level Goal.&lt;/p&gt;&lt;p&gt;Goals have access to a range of queries about the world through the Actor. As
far as I can tell most of these are pretty “low cost” from a performance
perspective. Aggro and Targeting seems to be handled outside, so it should be
possible to keep the Goals very lean even considering it’s all interpreted Lua.&lt;/p&gt;&lt;/section&gt;&lt;section id=&quot;actual-doing-stuff&quot;&gt;&lt;h1&gt;Actual Doing Stuff&lt;/h1&gt;&lt;p&gt;Something I’ve entirely skipped over is how the Goals actually Do things. For
the most part everything in &lt;em&gt;FROMSOFT&lt;/em&gt; games is animation driven.&lt;/p&gt;&lt;p&gt;The Goal says “play this attack animation”, and then the animation events carry
hitbox information and timings, special effect triggers, projectile creation
events, and whatnot. They also have a variety of “combo” features which seem to
boil down to choosing a different set of events in the animations to enable
faster linking of chained animation during a combo attack.&lt;/p&gt;&lt;p&gt;At some point they went all-in on Havok middleware. The animations are authored
with Havok Animation Studio (discontinued). Previously we mentioned the AI
scripts are using Havok Script (also discontinued). Physics is handled by
Havok’s physics, and pathfinding is delegated to Havok AI (not discontinued, but
renamed to Havok Navigation).&lt;/p&gt;&lt;/section&gt;&lt;section id=&quot;misc-stuff&quot;&gt;&lt;h1&gt;Misc Stuff&lt;/h1&gt;&lt;ol&gt;&lt;li&gt;&lt;p&gt;They seem to split AI scripting into a “logic” script, and a “battle” script,
where the logic script is far more sharable, and the battle scripts are often
bespoke. This seems super smart, it’s common to run into issues jamming both
these things into singular hierarchies.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Level designers are able to configure the Top Level Goal for an Actor in the
level itself, so you can place some enemies down with a passive Goal rather than
their usual combat Goal, and they would just chill whilst otherwise functioning
normally.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Most of the common code is relatively compact bits of Lua, but I believe load
bearing Goals like &lt;code&gt;Attack&lt;/code&gt; and &lt;code&gt;MoveToSomewhere&lt;/code&gt; are implemented in C++
which gives you a pretty nice balance of scriptability and performance sanity.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;The update function itself is sometimes used to check conditions, I expect
this must have caused problems occasionally. But so long as the interface for
Actors in scripting is thin I guess you can keep it under control. (Don’t add a
pathfind function call…)&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;I’ve entirely skipped over the event scripting system used to do high level
encounter logic and level scripting. Unlike the AI it seems to be entirely
custom, with a very restricted VM. That said, since it’s not Lua it’s hard to
see how they’re actually authored. If anyone knows of primary sources for info
about their tooling that would be super cool!&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;&lt;/section&gt;&lt;section id=&quot;conclusion&quot;&gt;&lt;h1&gt;Conclusion&lt;/h1&gt;&lt;p&gt;There’s a lot of enduring hype for complicated AI systems (&lt;a href=&quot;https://www.gamedevs.org/uploads/three-states-plan-ai-of-fear.pdf&quot;&gt;GOAP&lt;/a&gt; springs
to mind) but I think the success of putting a lot of control in the hands of your
designers and animators really speaks for itself.&lt;/p&gt;&lt;p&gt;A &lt;a href=&quot;https://en.wikipedia.org/wiki/Pushdown_automaton&quot;&gt;pushdown automaton&lt;/a&gt; is also fundamentally quite fast compared to
&lt;a href=&quot;https://www.gameaipro.com/GameAIPro/GameAIPro_Chapter06_The_Behavior_Tree_Starter_Kit.pdf&quot;&gt;Behavior Trees&lt;/a&gt; and planners. &lt;a href=&quot;https://www.gameaipro.com/GameAIPro/GameAIPro_Chapter06_The_Behavior_Tree_Starter_Kit.pdf&quot;&gt;Behavior Trees&lt;/a&gt; often require top-down re-evaluation of a complex
tree of scripted nodes, whereas this is almost always executing a single Goal
from the top of the stack&lt;a id=&quot;fnref4&quot; href=&quot;#fn4&quot; role=&quot;doc-noteref&quot; style=&quot;--anchor-name: --fnref4&quot;&gt;&lt;sup&gt;4&lt;/sup&gt;&lt;/a&gt;. Planners like &lt;a href=&quot;https://ai.stanford.edu/~nilsson/OnlinePubs-Nils/PublishedPapers/strips.pdf&quot;&gt;STRIPS&lt;/a&gt;, &lt;a href=&quot;https://www.gamedevs.org/uploads/three-states-plan-ai-of-fear.pdf&quot;&gt;GOAP&lt;/a&gt; and &lt;a href=&quot;https://en.wikipedia.org/wiki/Hierarchical_task_network&quot;&gt;HTN&lt;/a&gt; add an
expensive search to the middle of everything.&lt;/p&gt;&lt;p&gt;Compared to &lt;a href=&quot;https://en.wikipedia.org/wiki/Finite-state_machine&quot;&gt;FSMs&lt;/a&gt; the flexibility of dynamic transitions makes it easier
to avoid an explosion in the number of states and their transitions. This also
makes it far more reasonable to compose AI functionality in an imperative way.&lt;/p&gt;&lt;p&gt;Plus of course it’s dramatically more legible than planner based solutions where
individual actions are moved out of the hands of combat designers.&lt;/p&gt;&lt;p&gt;Is it going to handle more complex scenarios than the typical Soulsborne npc or
boss fight? I actually think it can go quite far.&lt;/p&gt;&lt;/section&gt;&lt;section id=&quot;update&quot;&gt;&lt;h1&gt;Update&lt;/h1&gt;&lt;p&gt;Some hackernews commenters were confused by the comparison with BTs, pointing
out that this scheme is quite similar to an event-based BT which keeps a stack
of active nodes, and wondering about the complexity compared to “normal” game
AI. Since I kind of phoned it in explaining all that stuff I’ll try to expand a
little.&lt;/p&gt;&lt;p&gt;Firstly while it’s true that event-based BTs do avoid the requirement to perform
a top-down re-execution of the entire tree, not all BT implemetations actually
work this way! Especially the more academic and early users look a more like the
naive implementation. I find the approach here which explicitly represents the
execution structure and builds it in imperative code to be significantly more
straightforward than trying to retrofit a kind of execution path cache on top of
a BT.&lt;/p&gt;&lt;p&gt;Secondly, with respect to the broader question of complexity, BTs implement
control flow structures inside the BT structure as nodes (again, especially so
in academic implementations), this is why you see BT terminology like
“Conditions”, “Sequences”, “Selectors” and “Parallels”. This significantly
bloats the size of trees, and the complexity of authoring them in my (limited!
I’m not an AI programmer) experience. By comparison, the &lt;em&gt;FROMSOFT&lt;/em&gt; style, even
today, has an extremely low state count, and relies on imperative code inside
those states to implement the majority of control flow. This, with my
performance hat on, is tremendously important for avoiding
death-by-a-thousand-cuts style issues where too much logic is held up in gnarly
tree structures that tend to be hard to manage during authoring and execution.&lt;/p&gt;&lt;p&gt;And finally, for large scale games the amount of code here is just low. &lt;em&gt;FROMSOFT&lt;/em&gt;
rely on a relatively large number of bespoke behaviors (one or more for each
boss, practically), but those behaviors are quite small compared to basically
anything you’d expect from other large AAA games. In production BTs elsewhere
one wouldn’t be surprised seeing trees of tens of thousands of nodes, on top of
hundreds of individual nodes implementing control flow and actions. Similarly
aspects like the data model on Actors is just extremely barebones here compared
to the rich data you’d perhaps expect to see in a “modern” BT implementation.&lt;/p&gt;&lt;p&gt;To reiterate a point from earlier as well, planners are just complex compared to
this, and FSMs often end up with a similar node explosion to BTs.&lt;/p&gt;&lt;p&gt;Where I talk about performance it’s important to consider it in the context of
implementation requirements. A “weekend” long implementation of the structure
used here (on top of a generic scripting language) is going to be basically
sufficient to implement a game like Elden Ring.&lt;a id=&quot;fnref5&quot; href=&quot;#fn5&quot; role=&quot;doc-noteref&quot; style=&quot;--anchor-name: --fnref5&quot;&gt;&lt;sup&gt;5&lt;/sup&gt;&lt;/a&gt; But if you want to
build a behavior tree implementation that hits similar performance goals, you’re
talking about building a system that lowers the tree as authored to an optimized
representation of some kind, on top of implementing a whole host of basic
control flow and data passing primitives, plus all the tooling required to
author, compose, and debug trees. The naive implementation is likely to be
insufficient.&lt;/p&gt;&lt;p&gt;So can you achieve good performance basically however you choose to design a
system like this? Yes. But as I see it you’re going to need to do a &lt;strong&gt;lot&lt;/strong&gt; more
work going the BT route.&lt;/p&gt;&lt;/section&gt;&lt;section id=&quot;references&quot;&gt;&lt;h1&gt;References&lt;/h1&gt;&lt;p&gt;Most of the info in this post comes from &lt;a href=&quot;https://eladidu.github.io/readable-ds-lua/&quot;&gt;eladidu readable ds lua&lt;/a&gt;
it’s fantastic and you can find many interesting definitions as well as a
little tutorial.&lt;/p&gt;&lt;p&gt;If you want to get even more excited there’s a bunch of tools for extracting
data from the game packages, as well as nice modding tools for patching things
here and there.&lt;/p&gt;&lt;/section&gt;&lt;section role=&quot;doc-endnotes&quot;&gt;&lt;ol&gt;&lt;li id=&quot;fn1&quot; style=&quot;--anchor: --fnref1&quot;&gt;&lt;p&gt;This is &lt;strong&gt;not&lt;/strong&gt; to be confused with the concept of a goal which you
might know from advanced planning systems like &lt;a href=&quot;https://ai.stanford.edu/~nilsson/OnlinePubs-Nils/PublishedPapers/strips.pdf&quot;&gt;STRIPS&lt;/a&gt;, &lt;a href=&quot;https://www.gamedevs.org/uploads/three-states-plan-ai-of-fear.pdf&quot;&gt;GOAP (Goal Oriented
Action Planning)&lt;/a&gt; (as seen in the classic shooter, F.E.A.R) or
&lt;a href=&quot;https://en.wikipedia.org/wiki/Hierarchical_task_network&quot;&gt;HTN (Hierarchical Task Networks)&lt;/a&gt;. Those systems use a search algorithm to
dynamically find a sequence of Actions which move the world into a Goal State.
There’s nothing remotely so complex happening here!&lt;a href=&quot;#fnref1&quot; role=&quot;doc-backlink&quot; style=&quot;style=&quot;--anchor-name: --fnref1&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;&lt;li id=&quot;fn2&quot; style=&quot;--anchor: --fnref2&quot;&gt;&lt;p&gt;Animation ids are largely based on playstation controller inputs
which are then offset by a per-actor value in the npc definition. Moveset swaps
can be performed by changing the offset dynamically from scripts!&lt;a href=&quot;#fnref2&quot; role=&quot;doc-backlink&quot; style=&quot;style=&quot;--anchor-name: --fnref2&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;&lt;li id=&quot;fn3&quot; style=&quot;--anchor: --fnref3&quot;&gt;&lt;p&gt;I’m glossing over a small problem… You want to be able to
write your scripts so that sub-goals function as a queue, not a stack, so they
are executed in the order they’re pushed. Unfortunately that slightly
complicates the implementation and explanation so I’ve left it as an exercise
for the reader.&lt;a href=&quot;#fnref3&quot; role=&quot;doc-backlink&quot; style=&quot;style=&quot;--anchor-name: --fnref3&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;&lt;li id=&quot;fn4&quot; style=&quot;--anchor: --fnref4&quot;&gt;&lt;p&gt;I’m not entirely sure whether they update only the current Goal
from the top, or whether they recursively update currently active goals, but I
suspect it might be the latter. This is still dramatically more efficient than
re-evaluating decision criteria in a behavior tree.&lt;a href=&quot;#fnref4&quot; role=&quot;doc-backlink&quot; style=&quot;style=&quot;--anchor-name: --fnref4&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;&lt;li id=&quot;fn5&quot; style=&quot;--anchor: --fnref5&quot;&gt;&lt;p&gt;Obviously this is a simplification, hopefully it’s clear I’m
talking about the core data structure and framework rather than the large amount
of peripheral work required to draw the rest of the owl.&lt;a href=&quot;#fnref5&quot; role=&quot;doc-backlink&quot; style=&quot;style=&quot;--anchor-name: --fnref5&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;&lt;/section&gt;</content>
</entry>
<entry>
<id>urn:uuid:019f1565-a722-7ba6-b85b-e30488aaee28</id>
<title>09 - December Adventure</title>
<author><name>Josh Simmons</name></author>
<updated>2025-12-09T00:00:00Z</updated>
<link href="https://nega.tv/posts/2025-december-adventure-09.html" rel="alternate" type="text/html" />
<content type="html">
&lt;p&gt;A quiet day today, a bit more Ast typing.&lt;/p&gt;&lt;p&gt;You can now do something like this…&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-rust&quot;&gt;&lt;span data-hl=&quot;keyword&quot;&gt;let&lt;/span&gt; arena = &lt;span data-hl=&quot;type&quot;&gt;Arena&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;::&lt;/span&gt;&lt;span data-hl=&quot;function&quot;&gt;new&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;(&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;)&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;;&lt;/span&gt;

&lt;span data-hl=&quot;keyword&quot;&gt;let&lt;/span&gt; a = arena&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;.&lt;/span&gt;&lt;span data-hl=&quot;function.method&quot;&gt;alloc&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;(&lt;/span&gt;&lt;span data-hl=&quot;type&quot;&gt;Expr&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;::&lt;/span&gt;&lt;span data-hl=&quot;type&quot;&gt;Literal&lt;/span&gt; &lt;span data-hl=&quot;punctuation.bracket&quot;&gt;{&lt;/span&gt;
    &lt;span data-hl=&quot;property&quot;&gt;literal&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;:&lt;/span&gt; arena&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;.&lt;/span&gt;&lt;span data-hl=&quot;function.method&quot;&gt;alloc&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;(&lt;/span&gt;&lt;span data-hl=&quot;type&quot;&gt;Literal&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;::&lt;/span&gt;&lt;span data-hl=&quot;type&quot;&gt;Integer&lt;/span&gt; &lt;span data-hl=&quot;punctuation.bracket&quot;&gt;{&lt;/span&gt; &lt;span data-hl=&quot;property&quot;&gt;value&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;:&lt;/span&gt; &lt;span data-hl=&quot;constant.builtin&quot;&gt;69&lt;/span&gt; &lt;span data-hl=&quot;punctuation.bracket&quot;&gt;}&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;)&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;,&lt;/span&gt;
&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;}&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;)&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;;&lt;/span&gt;

&lt;span data-hl=&quot;keyword&quot;&gt;let&lt;/span&gt; b = arena&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;.&lt;/span&gt;&lt;span data-hl=&quot;function.method&quot;&gt;alloc&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;(&lt;/span&gt;&lt;span data-hl=&quot;type&quot;&gt;Expr&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;::&lt;/span&gt;&lt;span data-hl=&quot;type&quot;&gt;Literal&lt;/span&gt; &lt;span data-hl=&quot;punctuation.bracket&quot;&gt;{&lt;/span&gt;
    &lt;span data-hl=&quot;property&quot;&gt;literal&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;:&lt;/span&gt; arena&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;.&lt;/span&gt;&lt;span data-hl=&quot;function.method&quot;&gt;alloc&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;(&lt;/span&gt;&lt;span data-hl=&quot;type&quot;&gt;Literal&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;::&lt;/span&gt;&lt;span data-hl=&quot;type&quot;&gt;Integer&lt;/span&gt; &lt;span data-hl=&quot;punctuation.bracket&quot;&gt;{&lt;/span&gt; &lt;span data-hl=&quot;property&quot;&gt;value&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;:&lt;/span&gt; &lt;span data-hl=&quot;constant.builtin&quot;&gt;42&lt;/span&gt; &lt;span data-hl=&quot;punctuation.bracket&quot;&gt;}&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;)&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;,&lt;/span&gt;
&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;}&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;)&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;;&lt;/span&gt;

&lt;span data-hl=&quot;keyword&quot;&gt;let&lt;/span&gt; expr = arena&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;.&lt;/span&gt;&lt;span data-hl=&quot;function.method&quot;&gt;alloc&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;(&lt;/span&gt;&lt;span data-hl=&quot;type&quot;&gt;Expr&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;::&lt;/span&gt;&lt;span data-hl=&quot;type&quot;&gt;Binary&lt;/span&gt; &lt;span data-hl=&quot;punctuation.bracket&quot;&gt;{&lt;/span&gt;
    &lt;span data-hl=&quot;property&quot;&gt;kind&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;:&lt;/span&gt; &lt;span data-hl=&quot;type&quot;&gt;BinaryOpKind&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;::&lt;/span&gt;&lt;span data-hl=&quot;constructor&quot;&gt;Add&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;,&lt;/span&gt;
    &lt;span data-hl=&quot;property&quot;&gt;lhs&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;:&lt;/span&gt; a&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;,&lt;/span&gt;
    &lt;span data-hl=&quot;property&quot;&gt;rhs&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;:&lt;/span&gt; b&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;,&lt;/span&gt;
&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;}&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;)&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;;&lt;/span&gt;

&lt;span data-hl=&quot;function.macro&quot;&gt;println&lt;/span&gt;&lt;span data-hl=&quot;function.macro&quot;&gt;!&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;(&lt;/span&gt;&lt;span data-hl=&quot;string&quot;&gt;&quot;{}&quot;&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;,&lt;/span&gt; expr&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;)&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;and it will output something like&lt;/p&gt;&lt;pre&gt;&lt;code&gt;(+ (69 42))
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;I rolled back on doing a more optimized ast structure for now, and just went
with something straightforward. Can fix the design later. Going to need some
helpers though, it’s very wordy trying to build the ast from that kind of arena
allocation and enum soup.&lt;/p&gt;</content>
</entry>
<entry>
<id>urn:uuid:019f1565-6e8c-7196-8a87-b5edcd4841c3</id>
<title>08 - December Adventure</title>
<author><name>Josh Simmons</name></author>
<updated>2025-12-08T00:00:00Z</updated>
<link href="https://nega.tv/posts/2025-december-adventure-08.html" rel="alternate" type="text/html" />
<content type="html">
&lt;p&gt;Day 8, Monday.&lt;/p&gt;&lt;p&gt;Not much today! I went for a run instead. Plaster looked good this afternoon
as well! A well deserved day of rest.&lt;/p&gt;</content>
</entry>
<entry>
<id>urn:uuid:019f1565-46cd-7a20-baf7-96ee8f1e354b</id>
<title>07 - December Adventure</title>
<author><name>Josh Simmons</name></author>
<updated>2025-12-07T00:00:00Z</updated>
<link href="https://nega.tv/posts/2025-december-adventure-07.html" rel="alternate" type="text/html" />
<content type="html">
&lt;p&gt;Day 7, Sunday.&lt;/p&gt;&lt;p&gt;This morning I rode (one of) the current apartment building’s cargo bike(s) out
to the hardware store to pick up a stapler. Then I went to the new apartment and
did the little bit of plaster work for the new arch. Plastering is really hard.
It took me a pretty tremendous amount of time to do the whole thing, and I’m
slightly worried that it’s going to require some serious sanding to get into
shape.&lt;/p&gt;&lt;p&gt;Hot tips (don’t follow this advice I’m bad):&lt;/p&gt;&lt;ol&gt;&lt;li&gt;Using a stapler to attach corner beads works great! Do that!&lt;/li&gt;&lt;li&gt;Paper tape is a tremendous fuck around, I can see why everyone uses mesh&lt;/li&gt;&lt;li&gt;Make sure you have a trowel / float that matches the work you’re doing&lt;/li&gt;&lt;li&gt;Extension of above, if you’re doing inner corners, maybe get a corner tool!&lt;/li&gt;&lt;li&gt;Issues with your initial framework leads to issues with your drywall leads to
issues with the plastering. So try to make sure everything is good from the
start&lt;/li&gt;&lt;li&gt;Try to minimize joins, and keep them aligned&lt;/li&gt;&lt;li&gt;Hire a plasterer lol&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;We’ll see tomorrow how it all looks set, I will be a bit sad if it looks rough.
The painters will also see my cursed creation tomorrow, so maybe they’ll get a
laugh out of it at least.&lt;/p&gt;&lt;p&gt;On the coding front I’m typing out the AST and parser. And I re-worked the lexer
to include diagnostic generation.&lt;/p&gt;&lt;p&gt;My intial version had the lexer emit error tokens, but that causes problems
when we try to parse a file which contains errors. So now an error in the number
literal scanner will emit a regular old number literal into the tokens output,
while also emitting a diagnostic pointing to that token and to the precise
error location. Nice!&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Tokens [
             0..9 Whitespace
            9..16 FloatLiteral: &#39;0x1.0p0&#39;
           16..25 Whitespace
           25..28 FloatLiteral: &#39;0e1&#39;
           28..37 Whitespace
           37..40 FloatLiteral: &#39;0.0&#39;
           40..49 Whitespace
           49..54 FloatLiteral: &#39;0.0e1&#39;
           54..63 Whitespace
           63..69 FloatLiteral: &#39;0x1.0p&#39;
           69..78 Whitespace
           78..85 FloatLiteral: &#39;0x1.0p-&#39;
           85..94 Whitespace
          94..114 FloatLiteral: &#39;0x1.921fb54442d18p+1&#39;
         114..123 Whitespace
         123..145 FloatLiteral: &#39;0x1.921fb54442d18p-999&#39;
         145..154 Whitespace
         154..166 IntegerLiteral: &#39;0b__________&#39;
         166..175 Whitespace
         175..177 IntegerLiteral: &#39;0b&#39;
         177..186 Whitespace
         186..192 IntegerLiteral: &#39;0b0102&#39;
         192..201 Whitespace
         201..204 FloatLiteral: &#39;2.2&#39;
         204..205 Period: &#39;.&#39;
         205..207 Name(10): &#39;_2&#39;
         207..216 Whitespace
                  Eof
]
Diagnostics [
        Error parsing FloatLiteral at offset 69: NumberLiteralNoExponentDigit
        Error parsing FloatLiteral at offset 85: NumberLiteralNoExponentDigit
        Error parsing IntegerLiteral at offset 166: NumberLiteralNoValidDigit
        Error parsing IntegerLiteral at offset 177: NumberLiteralNoValidDigit
        Error parsing IntegerLiteral at offset 192: NumberLiteralInvalidDigit { radix: Binary }
]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;I’ve also noticed that I’m randomly flipping between “scanner” / “scanning” and
“lexer” / “lexing”. I should probably pick a lane there, but I can’t choose.
lexer sounds better than scanner, but scan sounds better than tokenize or lex.&lt;/p&gt;&lt;p&gt;Unfortunately I didn’t get as far as I’d hoped on the coding adventure, but with
any luck the housework will calm down a little bit now.&lt;/p&gt;&lt;p&gt;Hah!&lt;/p&gt;</content>
</entry>
<entry>
<id>urn:uuid:019f1565-211c-7db7-aff4-0322c455f3c7</id>
<title>06 - December Adventure</title>
<author><name>Josh Simmons</name></author>
<updated>2025-12-06T00:00:00Z</updated>
<link href="https://nega.tv/posts/2025-december-adventure-06.html" rel="alternate" type="text/html" />
<content type="html">
&lt;p&gt;Today I knocked up a little extension to the square arch between the kitchen
and dining area. Previous owners had chopped off the bottom 3/4 of the arch, in
order to squeeze a second fridge in there. So we’re getting rid of the extra
fridge, and repairing the arch so it matches the other side of the room. So
that’s done now, and drywalled. Just going to do the plastering work tomorrow.&lt;/p&gt;&lt;p&gt;It’s a bit of a stressful thing. I’m fairly confident measuring, chopping and
screwing things together, but my experience plastering is almost zero. So
there’s a constant fight between one half of my brain which realizes that it’ll
be fine, and the other half which is trying to imagine every possible thing I
could have stupidly missed. Especially combined with the frustration of
translating things from / into Swedish when I don’t already know the exact thing
that I need.&lt;/p&gt;&lt;p&gt;Anyway, it will hopefully all be fine, and if not I’ll look like a goose. wcyd.&lt;/p&gt;&lt;p&gt;On the coding front, first up I realized that I had no “older article” and
“newer article” buttons on here. You can see them now though! Writing your own
website generator is both a blessing and a curse. (RSS feed any day now…)&lt;/p&gt;&lt;p&gt;Mostly finished with the grammar now, did expressions today. With any luck, I’ll
be writing the parser tomorrow.&lt;/p&gt;</content>
</entry>
<entry>
<id>urn:uuid:019f1565-01fd-7829-8feb-ad04e1bcc421</id>
<title>05 - December Adventure</title>
<author><name>Josh Simmons</name></author>
<updated>2025-12-05T00:00:00Z</updated>
<link href="https://nega.tv/posts/2025-december-adventure-05.html" rel="alternate" type="text/html" />
<content type="html">
&lt;p&gt;Went on a small outing to the hardware store. It’s easy to see how you can end
up with a garage full of tools, each used once. Anyway, resisted the urge to
buy a new cordless drill, and instead just bought some timber and plasterboard.&lt;/p&gt;&lt;p&gt;Nothing too exciting on the coding front. A little more work on the ast and
grammar.&lt;/p&gt;</content>
</entry>
<entry>
<id>urn:uuid:019f1564-dd94-7dd0-ade5-8c1b1d217c5e</id>
<title>04 - December Adventure</title>
<author><name>Josh Simmons</name></author>
<updated>2025-12-04T00:00:00Z</updated>
<link href="https://nega.tv/posts/2025-december-adventure-04.html" rel="alternate" type="text/html" />
<content type="html">
&lt;p&gt;Day 4, minor house work continues. Some part of me is starting to think it might
not be the smartest idea to burn the candle from 3 ends, by working on this, and
my actual job, and rennovating an apartment all at the same time.&lt;/p&gt;&lt;p&gt;Nah, it’s fine.&lt;/p&gt;&lt;p&gt;Today I added string interning and keyword identification to the lexer. Low
indicies in the string pool = keywords, high indices = everything else.&lt;/p&gt;</content>
</entry>
<entry>
<id>urn:uuid:019f1564-a7a2-7933-87e2-ec2f4e94a007</id>
<title>03 - December Adventure</title>
<author><name>Josh Simmons</name></author>
<updated>2025-12-03T00:00:00Z</updated>
<link href="https://nega.tv/posts/2025-december-adventure-03.html" rel="alternate" type="text/html" />
<content type="html">
&lt;p&gt;Day 3, no house work today.&lt;/p&gt;&lt;p&gt;I typed out some more grammar, and thought about lexing. Probably my approach
will be to just blast load all the source code at once, copying it into a source
collection. For starters I think I’ll manually load source files, but it would
be neat to be able to construct the full dependency tree from a single root
source file with a minimal recursive scan for imports.&lt;/p&gt;&lt;p&gt;Then I did a bit of noodling on lexer interning.&lt;/p&gt;</content>
</entry>
<entry>
<id>urn:uuid:019f1564-84bc-7175-95a4-94a2a026fd65</id>
<title>02 - December Adventure</title>
<author><name>Josh Simmons</name></author>
<updated>2025-12-02T00:00:00Z</updated>
<link href="https://nega.tv/posts/2025-december-adventure-02.html" rel="alternate" type="text/html" />
<content type="html">
&lt;p&gt;Today after work we went around to the new apartment again and measured up a
tiny bit of tiling and plaster work that we’re going to do in the kitchen, to
replace the old double-fridge-freezer cabinet. It’s going to be a decent amount
of faff, but the problem is pretty isolated. One annoying part of all this is
remembering that I don’t have any tools in this country. Not having tools turns
every job into a pain, who knew.&lt;/p&gt;&lt;p&gt;On the coding front, we’re back on the AST wagon. I’m currently settling on a
design that is just slightly more complex than “just use a huge enum”. The idea
is to use tagged indices into a series of vectors, which can then be improved
down the line.&lt;/p&gt;&lt;p&gt;I thought about adding string interning to the lexer as well, that’s probably
wise.&lt;/p&gt;</content>
</entry>
<entry>
<id>urn:uuid:019f1564-41c9-7ea6-84cf-081fc86b83aa</id>
<title>01 - December Adventure</title>
<author><name>Josh Simmons</name></author>
<updated>2025-12-01T00:00:00Z</updated>
<link href="https://nega.tv/posts/2025-december-adventure-01.html" rel="alternate" type="text/html" />
<content type="html">
&lt;p&gt;Today we got the keys to our new apartment, which was followed by ripping up a
floating floor, lifting all the beading from the skirting boards, taking down a
few shelves, and removing the fridge / freezer plus the crappy fridge / freezer
house.&lt;/p&gt;&lt;p&gt;Now there’s a small question about how to deal with the minor mess we’ve
created. Likely we’ll put in a single-width replacement fridge house, and throw
down an extra couple of tiles to cover the gap. Ideally we’d just throw down a
new kitchen, but at that point it’s starting to turn into a serious project.&lt;/p&gt;&lt;p&gt;So not very much shader compiler. However, I did start typing out the basic AST
structure.&lt;/p&gt;</content>
</entry>
<entry>
<id>urn:uuid:019f1563-870b-74b9-9ee6-a627d056154f</id>
<title>00 - December Adventure</title>
<author><name>Josh Simmons</name></author>
<updated>2025-11-30T00:00:00Z</updated>
<link href="https://nega.tv/posts/2025-december-adventure-00.html" rel="alternate" type="text/html" />
<content type="html">
&lt;p&gt;It is once again the time of year for holiday traditions and this year, once
again, I’m going to skip Advent of Code. Instead, I’ll attempt to do some kind
of &lt;a href=&quot;https://eli.li/december-adventure&quot;&gt;December Adventure&lt;/a&gt;, which I’m slightly
retconning into “doing whatever I want and then (hopefully) writing about it,
but in December”.&lt;/p&gt;&lt;p&gt;The tentative theme of this year’s December Adventure is… drumroll… shader
compilers! Why not claim the power for ourselves?&lt;/p&gt;&lt;p&gt;In other news, we sign for our new apartment tomorrow! So that might throw a
spanner in the works…&lt;/p&gt;</content>
</entry>
<entry>
<id>urn:uuid:019f1574-e2a4-762a-ab4e-aa16abe5c750</id>
<title>We Have Github At Home</title>
<author><name>Josh Simmons</name></author>
<updated>2025-08-25T00:00:00Z</updated>
<link href="https://nega.tv/posts/we-have-github-at-home.html" rel="alternate" type="text/html" />
<content type="html">
&lt;p&gt;The art of writing software is the art of willful underestimation, combined with
creative choice of goalposts. Last weekend I decided to quickly throw together
my own github.&lt;/p&gt;&lt;p&gt;Over the past few years I’ve found my enjoyment of the github platform rapidly
declining. It seems some boffin over at microsoft hq has commanded a hard pivot
to AI and we regular folk are to be left dealing with the fallout.&lt;/p&gt;&lt;p&gt;It’s also about time they start squeezing on cost and features. I would be happy
to pay for github (and in fact, I was happy paying), however, I’m not going to
pay for llm garbage. For me at least, github has become sourceforge.&lt;/p&gt;&lt;p&gt;But what to use instead?&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;&lt;a href=&quot;https://sourcehut.org/&quot;&gt;SourceHut&lt;/a&gt; is a bit much for me, mailing-list
driven workflows aren’t my vibe, and anyhow their lack of support for large
repositories is a non-starter for games.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;a href=&quot;https://codeberg.org/&quot;&gt;Codeberg&lt;/a&gt; is open-source exclusive, which is cool,
but I don’t want to be stuck with only public open-source repositories.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Self-hosted &lt;a href=&quot;https://about.gitlab.com/&quot;&gt;Gitlab&lt;/a&gt; is really just a pure clone
of github, LLM memes and all, which is not an improvement. It’s also just
pure overkill and complex to host.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;a href=&quot;https://forgejo.org/&quot;&gt;Forgejo&lt;/a&gt; has a cringe name.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;a href=&quot;https://bitbucket.org/product/&quot;&gt;Bitbucket&lt;/a&gt; is made by Atlassian… Plus more
LLM nonsense.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;a href=&quot;https://azure.microsoft.com/en-us/products/devops/repos&quot;&gt;Azure Git Repos&lt;/a&gt;
Just can’t manage to build a normal pricing page. Also under the microsoft
umbrella, so hard to imagine it not becoming regular github over time.&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;With the reasonable options out of the way, the only choice remaining is to build
and self-host my own software! The VPS which hosts this site is mostly idle
anyway, and there’s always room for one more hobby project in the clown car
called “free time”.&lt;/p&gt;&lt;p&gt;Let’s compare feature lists:&lt;/p&gt;&lt;table&gt;&lt;tr&gt;&lt;th&gt;&lt;/th&gt;&lt;th style=&quot;text-align: center;&quot;&gt;github&lt;/th&gt;&lt;th style=&quot;text-align: center;&quot;&gt;git.nega.tv&lt;/th&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Git Hosting&lt;/td&gt;&lt;td style=&quot;text-align: center;&quot;&gt;☑&lt;/td&gt;&lt;td style=&quot;text-align: center;&quot;&gt;☑&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;SSH Repo Access&lt;/td&gt;&lt;td style=&quot;text-align: center;&quot;&gt;☑&lt;/td&gt;&lt;td style=&quot;text-align: center;&quot;&gt;☑&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;HTTPS Repo Access&lt;/td&gt;&lt;td style=&quot;text-align: center;&quot;&gt;☑&lt;/td&gt;&lt;td style=&quot;text-align: center;&quot;&gt;☑&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Web Code View&lt;/td&gt;&lt;td style=&quot;text-align: center;&quot;&gt;☑&lt;/td&gt;&lt;td style=&quot;text-align: center;&quot;&gt;☑&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Issue Tracking&lt;/td&gt;&lt;td style=&quot;text-align: center;&quot;&gt;☑&lt;/td&gt;&lt;td style=&quot;text-align: center;&quot;&gt;☐&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Code Review&lt;/td&gt;&lt;td style=&quot;text-align: center;&quot;&gt;☑&lt;/td&gt;&lt;td style=&quot;text-align: center;&quot;&gt;☐&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Automation&lt;/td&gt;&lt;td style=&quot;text-align: center;&quot;&gt;☑&lt;/td&gt;&lt;td style=&quot;text-align: center;&quot;&gt;☐&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Project Wiki&lt;/td&gt;&lt;td style=&quot;text-align: center;&quot;&gt;☑&lt;/td&gt;&lt;td style=&quot;text-align: center;&quot;&gt;☐&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Analytics and Social&lt;/td&gt;&lt;td style=&quot;text-align: center;&quot;&gt;☑&lt;/td&gt;&lt;td style=&quot;text-align: center;&quot;&gt;☐&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;AI Dogshit&lt;/td&gt;&lt;td style=&quot;text-align: center;&quot;&gt;☑&lt;/td&gt;&lt;td style=&quot;text-align: center;&quot;&gt;☐&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;section id=&quot;ssh-access&quot;&gt;&lt;h1&gt;SSH Access&lt;/h1&gt;&lt;p&gt;Basic SSH access for git repos is trivial,&lt;/p&gt;&lt;ol&gt;&lt;li&gt;Create a linux user account for each git user.&lt;/li&gt;&lt;li&gt;Set their shell to &lt;a href=&quot;https://git-scm.com/docs/git-shell&quot;&gt;git-shell&lt;/a&gt;&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;However, that approach breaks down if you plan on more than a handful of users.
It also fails if you need fine-grained access control where multiple maintainers
can share a single account.&lt;/p&gt;&lt;p&gt;I had no plan for either of those things, but I wanted to support them
regardless. I also wanted to isoloate git access behind a ‘git’ user account in
the way that the Real Github does it. I’m a serious engineer.&lt;/p&gt;&lt;p&gt;Once again, there are a few turnkey options, most notably &lt;a href=&quot;https://github.com/tv42/gitosis&quot;&gt;gitosis&lt;/a&gt;
and &lt;a href=&quot;https://gitolite.com/gitolite/index.html&quot;&gt;gitolite&lt;/a&gt;, but I’ll roll my own.&lt;/p&gt;&lt;p&gt;The goal is to have a single git user, which everyone can access, and then to
filter repository access based on explicit permissions. There’s a handy feature
of &lt;a href=&quot;https://linux.die.net/man/8/sshd&quot;&gt;sshd&lt;/a&gt; which I use, &lt;code&gt;command=&lt;/code&gt; in the
&lt;code&gt;AuthorizedKeys&lt;/code&gt; file.&lt;/p&gt;&lt;p&gt;Essentially, instead of executing the user’s login shell (configured in
&lt;code&gt;/etc/passwd&lt;/code&gt;), I can configure per authorized key a specific command,
including arbitrary parameters, to run after authentication.&lt;/p&gt;&lt;p&gt;For example, the git user account’s &lt;code&gt;authorized_keys&lt;/code&gt; file might look like:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;command=&quot;/home/git/git-shell-multiplex josh&quot;,restrict sk-ecdsa-nsa-backdoor ...
command=&quot;/home/git/git-shell-multiplex sophie&quot;,restrict sk-ecdsa-nsa-backdoor ...
command=&quot;/home/git/git-shell-multiplex bazza69&quot;,restrict sk-ecdsa-nsa-backdoor ...
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Then, I implement &lt;em&gt;git-shell-multiplex&lt;/em&gt; (in Rust, of course) to run permission
checks and validate users are only using git commands. Rust is a questionable
choice for this kind of scripting, but I don’t let bad ideas get in the way of
doing whatever I like. The script is &lt;a href=&quot;https://git.nega.tv/?p=josh/git-shell-multiplex;a=blob;f=src/main.rs;h=db2d9cbc1c3dbf763a6204c8fc0a2b39f6c7e30f&quot;&gt;available here!&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;In order to define the repository permissions I’m currently reusing the &lt;code&gt;git-daemon-export-ok&lt;/code&gt;
marker file to enable read-access, and a new file, &lt;code&gt;git-shell-multiplex-contributors&lt;/code&gt;,
which contains a list of users with write access&lt;a id=&quot;fnref1&quot; href=&quot;#fn1&quot; role=&quot;doc-noteref&quot; style=&quot;--anchor-name: --fnref1&quot;&gt;&lt;sup&gt;1&lt;/sup&gt;&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;With that sorted, &lt;code&gt;git clone git@nega.tv:josh/narcissus.git&lt;/code&gt; and
&lt;code&gt;git push origin main&lt;/code&gt; work! Not too hard after all.&lt;/p&gt;&lt;p&gt;One downside is the configuration nightmare in the &lt;code&gt;authorized_keys&lt;/code&gt; file, but
I’m just one person so it’s not a big deal. You could also replace the
&lt;code&gt;authorized_keys&lt;/code&gt; &lt;em&gt;file&lt;/em&gt; with an &lt;code&gt;authorized_keys&lt;/code&gt; &lt;em&gt;command&lt;/em&gt; using
&lt;code&gt;AuthorizedKeysCommand&lt;/code&gt; in &lt;a href=&quot;https://linux.die.net/man/5/sshd_config&quot;&gt;sshd_config&lt;/a&gt;.
This would allow writing a single script to look up the appropriate keys and
configuration from a database shared with the multiplexer.&lt;/p&gt;&lt;/section&gt;&lt;section id=&quot;https-git-access&quot;&gt;&lt;h1&gt;HTTPS Git Access&lt;/h1&gt;&lt;p&gt;Git has two different &lt;a href=&quot;https://git-scm.com/docs/http-protocol&quot;&gt;http protocols&lt;/a&gt;,
a simple v1 protocol, and a smart v2 protocol. Since I don’t care about pushing
over https, I just setup &lt;a href=&quot;https://git-scm.com/docs/git-http-backend&quot;&gt;git-http-backend&lt;/a&gt;
in read-only configuration.&lt;/p&gt;&lt;p&gt;&lt;code&gt;nginx.conf (excerpt)&lt;/code&gt;&lt;/p&gt;&lt;pre&gt;&lt;code&gt;# requests that need to go to git-http-backend
location ~ ^(.*)\.git/(HEAD|info/refs|objects/info/.*|git-(upload|receive)-pack)$ {
    include fastcgi_params;
    gzip off;
    fastcgi_param SCRIPT_FILENAME   /usr/libexec/git-core/git-http-backend;
    fastcgi_param PATH_INFO         $1/$2;
    fastcgi_param GIT_PROJECT_ROOT  /var/git;
    fastcgi_param REMOTE_USER $remote_user;
    fastcgi_pass unix:/run/fcgiwrap.sock;
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;I filter out a bunch of git-specific paths and punt them to &lt;em&gt;git-http-backend&lt;/em&gt;
over fastcgi. Of special note are the captures in the location regex; they let
me drop the &lt;code&gt;.git&lt;/code&gt; suffix when constructing &lt;code&gt;PATH_INFO&lt;/code&gt; so an incoming url like
&lt;code&gt;https://git.nega.tv/josh/git-shell-multiplex.git&lt;/code&gt; invokes &lt;em&gt;git-http-backend&lt;/em&gt;
without the suffix, finding the actual repository at &lt;code&gt;/var/git/josh/git-shell-multiplex/&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;&lt;em&gt;git-http-backend&lt;/em&gt; looks for the file &lt;code&gt;git-daemon-export-ok&lt;/code&gt; in a repo, and only
allows access to those with the marker file. This leaks the existance of private
repos, since it reports ‘no permissions’ rather than ‘not found’ but for my
purposes this isn’t a big deal.&lt;/p&gt;&lt;/section&gt;&lt;section id=&quot;web-interface&quot;&gt;&lt;h1&gt;Web Interface&lt;/h1&gt;&lt;p&gt;There are two simple options for a web based repository viewer,
&lt;a href=&quot;https://git.zx2c4.com/cgit/about/&quot;&gt;cgit&lt;/a&gt;, and
&lt;a href=&quot;https://git-scm.com/docs/gitweb&quot;&gt;gitweb&lt;/a&gt;. Since &lt;em&gt;gitweb&lt;/em&gt; is hosted as part of the
git distribution I use that one, but honestly I’m not super excited about
either option. This also requires fastcgi.&lt;/p&gt;&lt;p&gt;&lt;code&gt;nginx.conf (excerpt)&lt;/code&gt;&lt;/p&gt;&lt;pre&gt;&lt;code&gt;location /gitweb.cgi {
    include fastcgi_params;
    gzip off;
    fastcgi_param SCRIPT_FILENAME    /var/www/git/gitweb.cgi;
    fastcgi_param PATH_INFO          $uri;
    fastcgi_param GITWEB_CONFIG      /etc/gitweb.conf;
    fastcgi_pass unix:/run/fcgiwrap.sock;
}

location / {
    root /var/www/git;
    index gitweb.cgi;
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;There’s nothing exciting in the config here. In &lt;code&gt;gitweb.conf&lt;/code&gt; I enable syntax
highlighting and blame, but that’s it. In the same way as &lt;em&gt;git-http-backend&lt;/em&gt;,
&lt;em&gt;gitweb&lt;/em&gt; is configured to only expose repositories which contain the
&lt;code&gt;git-daemon-export-ok&lt;/code&gt; marker file.&lt;/p&gt;&lt;/section&gt;&lt;section id=&quot;fastcgi-setup&quot;&gt;&lt;h1&gt;FastCGI Setup&lt;/h1&gt;&lt;p&gt;Honestly I’m somewhat baffled by the fact that CGI still exists. One of the
first work tasks I ever was given was configuring CGI scripts, and somehow this
practice continues in the current day. Incredible.&lt;/p&gt;&lt;p&gt;Both &lt;em&gt;gitweb&lt;/em&gt;, and &lt;em&gt;git-http-backend&lt;/em&gt; use FastCGI, so I set it up to play
nice with the git user and the httpd user. For sanity, I set this up as a
systemd service triggered by a systemd socket.&lt;/p&gt;&lt;p&gt;&lt;code&gt;/etc/systemd/system/fcgiwrap.socket&lt;/code&gt;&lt;/p&gt;&lt;pre&gt;&lt;code&gt;[Unit]
Description=fcgiwrap Socket

[Socket]
ListenStream=/run/fcgiwrap.sock

[Install]
WantedBy=sockets.target
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;/etc/systemd/system/fcgiwrap.service&lt;/code&gt;&lt;/p&gt;&lt;pre&gt;&lt;code&gt;[Unit]
Description=Simple CGI Server
After=nss-user-lookup.target

[Service]
ExecStart=/usr/sbin/fcgiwrap
User=git
Group=nginx
StandardError=syslog

[Install]
Also=fcgiwrap.socket
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Take note of the user and group. git tooling doesn’t like repositories that it
doesn’t own, so I make the owner ‘git’, and nginx needs access to the unix socket,
so I use the ‘nginx’ group. This is probably not ideal - you might want to change
the groups of each user instead.&lt;/p&gt;&lt;p&gt;Also note the &lt;code&gt;StandardError=syslog&lt;/code&gt; line. It’s important when trying to debug.&lt;/p&gt;&lt;/section&gt;&lt;section id=&quot;future-work&quot;&gt;&lt;h1&gt;Future Work&lt;/h1&gt;&lt;p&gt;I’ve duct-taped enough software together to create the basics of a scm host. It’s
good enough for me, but I wouldn’t mind having some more bells and whistles.&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;Issue Tracker.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Code Review.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Build automation, and BORS equivalent.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Web view with code analysis. Would be nice to somehow plug rust-analyzer into
the code view so it’s actually explorable.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Beautiful Rust monolith web application instead of nightmare configuration
file soup.&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;However these are all significantly more work than is achievable in a single
weekend; they’ll have to wait until next weekend.&lt;/p&gt;&lt;p&gt;You can find the fruits of my labors over at &lt;a href=&quot;https://git.nega.tv/&quot;&gt;git.nega.tv&lt;/a&gt;&lt;/p&gt;&lt;/section&gt;&lt;section role=&quot;doc-endnotes&quot;&gt;&lt;ol&gt;&lt;li id=&quot;fn1&quot; style=&quot;--anchor: --fnref1&quot;&gt;&lt;p&gt;This is &lt;strong&gt;in-addition&lt;/strong&gt; to the account which contains the
repository. So ‘josh’ always has full access to ‘josh/repo.git’&lt;a href=&quot;#fnref1&quot; role=&quot;doc-backlink&quot; style=&quot;style=&quot;--anchor-name: --fnref1&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;&lt;/section&gt;</content>
</entry>
<entry>
<id>urn:uuid:019f1567-8acf-74c5-8b7f-4bc63175d6d3</id>
<title>We Have A Vulkan Memory Allocator At Home</title>
<author><name>Josh Simmons</name></author>
<updated>2025-08-09T00:00:00Z</updated>
<link href="https://nega.tv/posts/we-have-a-vulkan-memory-allocator-at-home.html" rel="alternate" type="text/html" />
<content type="html">
&lt;p&gt;Vulkan’s built-in allocation functionality is extremely limited, implementations
are:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Only required to support up to 4096 individual allocations. With the
concrete limit reported dynamically.&lt;/li&gt;&lt;li&gt;Required to align returned memory conservatively for the worst-case
alignment of all resource types.&lt;/li&gt;&lt;li&gt;Responsible for synchronizing concurrent allocations&lt;a id=&quot;fnref1&quot; href=&quot;#fn1&quot; role=&quot;doc-noteref&quot; style=&quot;--anchor-name: --fnref1&quot;&gt;&lt;sup&gt;1&lt;/sup&gt;&lt;/a&gt;.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;These issues typically require the user to implement some form of memory
allocator to sub-allocate resources from larger memory blocks. In this
adventure, rather than doing the smart thing and using
&lt;a href=&quot;https://gpuopen.com/vulkan-memory-allocator/&quot;&gt;Vulkan Memory Allocator&lt;/a&gt;, we’ll be rolling our own
from scratch!&lt;/p&gt;&lt;p&gt;Our goal is to allocate a smaller number of large &lt;strong&gt;super-blocks&lt;/strong&gt; directly from
the driver, and from those &lt;strong&gt;super-blocks&lt;/strong&gt;, &lt;strong&gt;sub-allocate&lt;/strong&gt; to service individual
allocation requests from the application. There’s a variety of practical options
here, but one very common choice is TLSF.&lt;/p&gt;&lt;section id=&quot;tlsf&quot;&gt;&lt;h1&gt;TLSF&lt;/h1&gt;&lt;p&gt;&lt;a href=&quot;http://www.gii.upv.es/tlsf/files/papers/ecrts04_tlsf.pdf&quot;&gt;TLSF&lt;/a&gt; is a simple,
fast, constant time allocator algorithm designed primarily for embedded
use-cases. Empty blocks are stored in a series of free-lists, using a
&lt;a href=&quot;https://pvk.ca/Blog/2015/06/27/linear-log-bucketing-fast-versatile-simple/&quot;&gt;linear-log&lt;/a&gt; sequence of bucket sizes to bound internal
fragmentation and minimise external fragmentation. A two-level bitmap
acceleration structure tracks which free-lists contain blocks, allowing good-fit
searches to complete using a couple of bit instructions widely supported by
modern CPUs.&lt;/p&gt;&lt;p&gt;Memory is passed to TLSF as &lt;strong&gt;super-blocks&lt;/strong&gt; representing large chunks of
contiguous memory. The allocator then operates by subdividing those
&lt;strong&gt;super-blocks&lt;/strong&gt; into &lt;strong&gt;blocks&lt;/strong&gt; representing both individual allocations and free
regions.&lt;/p&gt;&lt;/section&gt;&lt;section id=&quot;binning&quot;&gt;&lt;h1&gt;Binning&lt;/h1&gt;&lt;p&gt;Searching all unused blocks to find the most suitable block to allocate from
would be prohibitively expensive so unused blocks are stored in a set of
free-lists seggregated by their size. The strategy we used to seggregate into
bins must strike a delicate balance:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;It must be fast to calculate the bin for a given size.&lt;/li&gt;&lt;li&gt;Bin sizes must approximate allocation sizes to minimize fragmentation.&lt;/li&gt;&lt;li&gt;The number of bins should ideally be small and bounded.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;To achieve these goals we’ll use a common technique,
&lt;a href=&quot;https://pvk.ca/Blog/2015/06/27/linear-log-bucketing-fast-versatile-simple/&quot;&gt;linear-log bucketing&lt;/a&gt;.&lt;/p&gt;&lt;/section&gt;&lt;section id=&quot;allocation&quot;&gt;&lt;h1&gt;Allocation&lt;/h1&gt;&lt;p&gt;Allocation proceeds by scanning the bitmap acceleration structure, if any
suitable blocks are found, that block is immediately used to service the
allocation. If there would be significant left over space, the block will be
split and the left-over memory inserted back into the free-lists.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-rust&quot;&gt;&lt;span data-hl=&quot;comment.documentation&quot;&gt;/// Search the acceleration structure for a non-empty list suitable for an&lt;/span&gt;
&lt;span data-hl=&quot;comment.documentation&quot;&gt;/// allocation of the given size.&lt;/span&gt;
&lt;span data-hl=&quot;comment.documentation&quot;&gt;///&lt;/span&gt;
&lt;span data-hl=&quot;comment.documentation&quot;&gt;/// Returns the rounded size and bin index if a non-empty list is found, or&lt;/span&gt;
&lt;span data-hl=&quot;comment.documentation&quot;&gt;/// None&lt;/span&gt;
&lt;span data-hl=&quot;keyword&quot;&gt;fn&lt;/span&gt; &lt;span data-hl=&quot;function&quot;&gt;search_non_empty_bin&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;(&lt;/span&gt;&lt;span data-hl=&quot;operator&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span data-hl=&quot;variable.builtin&quot;&gt;self&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;,&lt;/span&gt; &lt;span data-hl=&quot;variable.parameter&quot;&gt;size&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;:&lt;/span&gt; &lt;span data-hl=&quot;type.builtin&quot;&gt;u32&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;)&lt;/span&gt; -&amp;gt; &lt;span data-hl=&quot;type&quot;&gt;Option&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;(&lt;/span&gt;&lt;span data-hl=&quot;type.builtin&quot;&gt;u32&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;,&lt;/span&gt; &lt;span data-hl=&quot;type&quot;&gt;Bin&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;)&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span data-hl=&quot;punctuation.bracket&quot;&gt;{&lt;/span&gt;
    &lt;span data-hl=&quot;comment&quot;&gt;// We need to find the bin which contains only empty-blocks large enough for the&lt;/span&gt;
    &lt;span data-hl=&quot;comment&quot;&gt;// given size because we unconditionally use the first empty block found. So&lt;/span&gt;
    &lt;span data-hl=&quot;comment&quot;&gt;// this must round up.&lt;/span&gt;
    &lt;span data-hl=&quot;keyword&quot;&gt;let&lt;/span&gt; &lt;span data-hl=&quot;punctuation.bracket&quot;&gt;(&lt;/span&gt;rounded_size&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;,&lt;/span&gt; starting_bin&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;)&lt;/span&gt; = &lt;span data-hl=&quot;type&quot;&gt;Bin&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;::&lt;/span&gt;&lt;span data-hl=&quot;function&quot;&gt;from_size_round_up&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;(&lt;/span&gt;size&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;)&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;;&lt;/span&gt;

    &lt;span data-hl=&quot;function.macro&quot;&gt;println&lt;/span&gt;&lt;span data-hl=&quot;function.macro&quot;&gt;!&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;(&lt;/span&gt;&lt;span data-hl=&quot;string&quot;&gt;&quot;Hello, World!&quot;&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;)&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;;&lt;/span&gt;

    &lt;span data-hl=&quot;keyword&quot;&gt;let&lt;/span&gt; &lt;span data-hl=&quot;keyword&quot;&gt;mut&lt;/span&gt; bin = starting_bin&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;.&lt;/span&gt;&lt;span data-hl=&quot;function.method&quot;&gt;bin&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;(&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;)&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;;&lt;/span&gt;
    &lt;span data-hl=&quot;keyword&quot;&gt;let&lt;/span&gt; sub_bin = starting_bin&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;.&lt;/span&gt;&lt;span data-hl=&quot;function.method&quot;&gt;sub_bin&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;(&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;)&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;;&lt;/span&gt;

    &lt;span data-hl=&quot;comment&quot;&gt;// First we scan the second-level bitmap from sub_bin, masking out the earlier&lt;/span&gt;
    &lt;span data-hl=&quot;comment&quot;&gt;// sub-bins so we don&#39;t end up returning a bin that&#39;s too small for the&lt;/span&gt;
    &lt;span data-hl=&quot;comment&quot;&gt;// allocation.&lt;/span&gt;
    &lt;span data-hl=&quot;keyword&quot;&gt;let&lt;/span&gt; &lt;span data-hl=&quot;keyword&quot;&gt;mut&lt;/span&gt; second_level = &lt;span data-hl=&quot;variable.builtin&quot;&gt;self&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;.&lt;/span&gt;&lt;span data-hl=&quot;property&quot;&gt;bitmap_1&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;[&lt;/span&gt;bin&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;.&lt;/span&gt;&lt;span data-hl=&quot;function.method&quot;&gt;widen&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;(&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;)&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;]&lt;/span&gt; &lt;span data-hl=&quot;operator&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span data-hl=&quot;punctuation.bracket&quot;&gt;(&lt;/span&gt;!&lt;span data-hl=&quot;constant.builtin&quot;&gt;0&lt;/span&gt; &amp;lt;&amp;lt; sub_bin&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;)&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;;&lt;/span&gt;

    &lt;span data-hl=&quot;comment&quot;&gt;// If that search failed, then we must scan the first-level bitmap from the next&lt;/span&gt;
    &lt;span data-hl=&quot;comment&quot;&gt;// bin forward. If we find anything here it cannot possibly be smaller than the&lt;/span&gt;
    &lt;span data-hl=&quot;comment&quot;&gt;// requested allocation.&lt;/span&gt;
    &lt;span data-hl=&quot;keyword&quot;&gt;if&lt;/span&gt; second_level == &lt;span data-hl=&quot;constant.builtin&quot;&gt;0&lt;/span&gt; &lt;span data-hl=&quot;punctuation.bracket&quot;&gt;{&lt;/span&gt;
        &lt;span data-hl=&quot;keyword&quot;&gt;let&lt;/span&gt; first_level = &lt;span data-hl=&quot;variable.builtin&quot;&gt;self&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;.&lt;/span&gt;&lt;span data-hl=&quot;property&quot;&gt;bitmap_0&lt;/span&gt; &lt;span data-hl=&quot;operator&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span data-hl=&quot;punctuation.bracket&quot;&gt;(&lt;/span&gt;!&lt;span data-hl=&quot;constant.builtin&quot;&gt;0&lt;/span&gt; &amp;lt;&amp;lt; &lt;span data-hl=&quot;punctuation.bracket&quot;&gt;(&lt;/span&gt;bin + &lt;span data-hl=&quot;constant.builtin&quot;&gt;1&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;)&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;)&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;;&lt;/span&gt;

        &lt;span data-hl=&quot;comment&quot;&gt;// If that search also failed, there&#39;s no suitable blocks.&lt;/span&gt;
        &lt;span data-hl=&quot;keyword&quot;&gt;if&lt;/span&gt; first_level == &lt;span data-hl=&quot;constant.builtin&quot;&gt;0&lt;/span&gt; &lt;span data-hl=&quot;punctuation.bracket&quot;&gt;{&lt;/span&gt;
            &lt;span data-hl=&quot;keyword&quot;&gt;return&lt;/span&gt; &lt;span data-hl=&quot;constructor&quot;&gt;None&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;;&lt;/span&gt;
        &lt;span data-hl=&quot;punctuation.bracket&quot;&gt;}&lt;/span&gt;

        &lt;span data-hl=&quot;comment&quot;&gt;// Recalculate the bin from the first level bitmap.&lt;/span&gt;
        bin = first_level&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;.&lt;/span&gt;&lt;span data-hl=&quot;function.method&quot;&gt;trailing_zeros&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;(&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;)&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;;&lt;/span&gt;
        second_level = &lt;span data-hl=&quot;variable.builtin&quot;&gt;self&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;.&lt;/span&gt;&lt;span data-hl=&quot;property&quot;&gt;bitmap_1&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;[&lt;/span&gt;bin&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;.&lt;/span&gt;&lt;span data-hl=&quot;function.method&quot;&gt;widen&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;(&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;)&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;]&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;;&lt;/span&gt;
    &lt;span data-hl=&quot;punctuation.bracket&quot;&gt;}&lt;/span&gt;

    &lt;span data-hl=&quot;comment&quot;&gt;// Find the sub-bin from the second level bitmap.&lt;/span&gt;
    &lt;span data-hl=&quot;keyword&quot;&gt;let&lt;/span&gt; sub_bin = second_level&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;.&lt;/span&gt;&lt;span data-hl=&quot;function.method&quot;&gt;trailing_zeros&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;(&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;)&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;;&lt;/span&gt;
    &lt;span data-hl=&quot;function&quot;&gt;Some&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;(&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;(&lt;/span&gt;rounded_size&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;,&lt;/span&gt; &lt;span data-hl=&quot;type&quot;&gt;Bin&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;::&lt;/span&gt;&lt;span data-hl=&quot;function&quot;&gt;new&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;(&lt;/span&gt;bin&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;,&lt;/span&gt; sub_bin&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;)&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;)&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;)&lt;/span&gt;
&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Freeing allocated memory does some cusory validation of the allocation to
prevent trivial double-frees, then returns the block to the free-lists. Adjacent
free physical blocks are merged immediately.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-rust&quot;&gt;&lt;span data-hl=&quot;keyword&quot;&gt;pub&lt;/span&gt; &lt;span data-hl=&quot;keyword&quot;&gt;fn&lt;/span&gt; &lt;span data-hl=&quot;function&quot;&gt;free&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;(&lt;/span&gt;&lt;span data-hl=&quot;operator&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span data-hl=&quot;keyword&quot;&gt;mut&lt;/span&gt; &lt;span data-hl=&quot;variable.builtin&quot;&gt;self&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;,&lt;/span&gt; &lt;span data-hl=&quot;variable.parameter&quot;&gt;allocation&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;:&lt;/span&gt; &lt;span data-hl=&quot;type&quot;&gt;Allocation&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span data-hl=&quot;type&quot;&gt;T&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;)&lt;/span&gt; &lt;span data-hl=&quot;punctuation.bracket&quot;&gt;{&lt;/span&gt;
    &lt;span data-hl=&quot;keyword&quot;&gt;let&lt;/span&gt; &lt;span data-hl=&quot;keyword&quot;&gt;mut&lt;/span&gt; block_index = allocation&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;.&lt;/span&gt;&lt;span data-hl=&quot;property&quot;&gt;block_index&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;;&lt;/span&gt;
    &lt;span data-hl=&quot;keyword&quot;&gt;let&lt;/span&gt; generation = &lt;span data-hl=&quot;variable.builtin&quot;&gt;self&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;.&lt;/span&gt;&lt;span data-hl=&quot;property&quot;&gt;blocks&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;[&lt;/span&gt;block_index&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;]&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;.&lt;/span&gt;&lt;span data-hl=&quot;property&quot;&gt;generation&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;;&lt;/span&gt;
    &lt;span data-hl=&quot;function.macro&quot;&gt;assert_eq&lt;/span&gt;&lt;span data-hl=&quot;function.macro&quot;&gt;!&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;(&lt;/span&gt;generation&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;,&lt;/span&gt; allocation&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;.&lt;/span&gt;generation&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;,&lt;/span&gt; &lt;span data-hl=&quot;string&quot;&gt;&quot;double-free&quot;&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;)&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;;&lt;/span&gt;
    &lt;span data-hl=&quot;variable.builtin&quot;&gt;self&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;.&lt;/span&gt;&lt;span data-hl=&quot;property&quot;&gt;blocks&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;[&lt;/span&gt;block_index&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;]&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;.&lt;/span&gt;&lt;span data-hl=&quot;property&quot;&gt;generation&lt;/span&gt; = generation&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;.&lt;/span&gt;&lt;span data-hl=&quot;function.method&quot;&gt;wrapping_add&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;(&lt;/span&gt;&lt;span data-hl=&quot;constant.builtin&quot;&gt;1&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;)&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;;&lt;/span&gt;

    &lt;span data-hl=&quot;comment&quot;&gt;// Merge next block into the current block.&lt;/span&gt;
    &lt;span data-hl=&quot;punctuation.bracket&quot;&gt;{&lt;/span&gt;
        &lt;span data-hl=&quot;keyword&quot;&gt;let&lt;/span&gt; into_block_index = block_index&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;;&lt;/span&gt;
        &lt;span data-hl=&quot;keyword&quot;&gt;let&lt;/span&gt; from_block_index = &lt;span data-hl=&quot;variable.builtin&quot;&gt;self&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;.&lt;/span&gt;&lt;span data-hl=&quot;property&quot;&gt;blocks&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;[&lt;/span&gt;block_index&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;]&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;.&lt;/span&gt;&lt;span data-hl=&quot;property&quot;&gt;phys_link&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;.&lt;/span&gt;&lt;span data-hl=&quot;property&quot;&gt;next&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;;&lt;/span&gt;
        &lt;span data-hl=&quot;keyword&quot;&gt;if&lt;/span&gt; &lt;span data-hl=&quot;variable.builtin&quot;&gt;self&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;.&lt;/span&gt;&lt;span data-hl=&quot;function.method&quot;&gt;can_merge_block_left&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;(&lt;/span&gt;into_block_index&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;,&lt;/span&gt; from_block_index&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;)&lt;/span&gt; &lt;span data-hl=&quot;punctuation.bracket&quot;&gt;{&lt;/span&gt;
            &lt;span data-hl=&quot;keyword&quot;&gt;let&lt;/span&gt; from_size = &lt;span data-hl=&quot;variable.builtin&quot;&gt;self&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;.&lt;/span&gt;&lt;span data-hl=&quot;property&quot;&gt;blocks&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;[&lt;/span&gt;from_block_index&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;]&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;.&lt;/span&gt;&lt;span data-hl=&quot;property&quot;&gt;size&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;;&lt;/span&gt;
            &lt;span data-hl=&quot;variable.builtin&quot;&gt;self&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;.&lt;/span&gt;&lt;span data-hl=&quot;function.method&quot;&gt;extract_block&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;(&lt;/span&gt;from_block_index&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;)&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;;&lt;/span&gt;
            &lt;span data-hl=&quot;function.macro&quot;&gt;list_unlink&lt;/span&gt;&lt;span data-hl=&quot;function.macro&quot;&gt;!&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;(&lt;/span&gt;&lt;span data-hl=&quot;variable.builtin&quot;&gt;self&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;.&lt;/span&gt;blocks&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;,&lt;/span&gt; phys_link&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;,&lt;/span&gt; from_block_index&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;)&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;;&lt;/span&gt;
            &lt;span data-hl=&quot;variable.builtin&quot;&gt;self&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;.&lt;/span&gt;&lt;span data-hl=&quot;function.method&quot;&gt;recycle_block&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;(&lt;/span&gt;from_block_index&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;)&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;;&lt;/span&gt;
            &lt;span data-hl=&quot;variable.builtin&quot;&gt;self&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;.&lt;/span&gt;&lt;span data-hl=&quot;property&quot;&gt;blocks&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;[&lt;/span&gt;into_block_index&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;]&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;.&lt;/span&gt;&lt;span data-hl=&quot;property&quot;&gt;size&lt;/span&gt; += from_size&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;;&lt;/span&gt;
        &lt;span data-hl=&quot;punctuation.bracket&quot;&gt;}&lt;/span&gt;
    &lt;span data-hl=&quot;punctuation.bracket&quot;&gt;}&lt;/span&gt;

    &lt;span data-hl=&quot;comment&quot;&gt;// Merge current block into the prev block.&lt;/span&gt;
    &lt;span data-hl=&quot;punctuation.bracket&quot;&gt;{&lt;/span&gt;
        &lt;span data-hl=&quot;keyword&quot;&gt;let&lt;/span&gt; into_block_index = &lt;span data-hl=&quot;variable.builtin&quot;&gt;self&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;.&lt;/span&gt;&lt;span data-hl=&quot;property&quot;&gt;blocks&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;[&lt;/span&gt;block_index&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;]&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;.&lt;/span&gt;&lt;span data-hl=&quot;property&quot;&gt;phys_link&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;.&lt;/span&gt;&lt;span data-hl=&quot;property&quot;&gt;prev&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;;&lt;/span&gt;
        &lt;span data-hl=&quot;keyword&quot;&gt;let&lt;/span&gt; from_block_index = block_index&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;;&lt;/span&gt;
        &lt;span data-hl=&quot;keyword&quot;&gt;if&lt;/span&gt; &lt;span data-hl=&quot;variable.builtin&quot;&gt;self&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;.&lt;/span&gt;&lt;span data-hl=&quot;function.method&quot;&gt;can_merge_block_left&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;(&lt;/span&gt;into_block_index&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;,&lt;/span&gt; from_block_index&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;)&lt;/span&gt; &lt;span data-hl=&quot;punctuation.bracket&quot;&gt;{&lt;/span&gt;
            &lt;span data-hl=&quot;keyword&quot;&gt;let&lt;/span&gt; from_size = &lt;span data-hl=&quot;variable.builtin&quot;&gt;self&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;.&lt;/span&gt;&lt;span data-hl=&quot;property&quot;&gt;blocks&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;[&lt;/span&gt;from_block_index&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;]&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;.&lt;/span&gt;&lt;span data-hl=&quot;property&quot;&gt;size&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;;&lt;/span&gt;
            &lt;span data-hl=&quot;variable.builtin&quot;&gt;self&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;.&lt;/span&gt;&lt;span data-hl=&quot;function.method&quot;&gt;extract_block&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;(&lt;/span&gt;into_block_index&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;)&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;;&lt;/span&gt;
            &lt;span data-hl=&quot;function.macro&quot;&gt;list_unlink&lt;/span&gt;&lt;span data-hl=&quot;function.macro&quot;&gt;!&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;(&lt;/span&gt;&lt;span data-hl=&quot;variable.builtin&quot;&gt;self&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;.&lt;/span&gt;blocks&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;,&lt;/span&gt; phys_link&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;,&lt;/span&gt; from_block_index&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;)&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;;&lt;/span&gt;
            &lt;span data-hl=&quot;variable.builtin&quot;&gt;self&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;.&lt;/span&gt;&lt;span data-hl=&quot;function.method&quot;&gt;recycle_block&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;(&lt;/span&gt;from_block_index&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;)&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;;&lt;/span&gt;
            &lt;span data-hl=&quot;variable.builtin&quot;&gt;self&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;.&lt;/span&gt;&lt;span data-hl=&quot;property&quot;&gt;blocks&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;[&lt;/span&gt;into_block_index&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;]&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;.&lt;/span&gt;&lt;span data-hl=&quot;property&quot;&gt;size&lt;/span&gt; += from_size&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;;&lt;/span&gt;
            block_index = into_block_index&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;;&lt;/span&gt;
        &lt;span data-hl=&quot;punctuation.bracket&quot;&gt;}&lt;/span&gt;
    &lt;span data-hl=&quot;punctuation.bracket&quot;&gt;}&lt;/span&gt;

    &lt;span data-hl=&quot;comment&quot;&gt;// Insert the merged free block.&lt;/span&gt;
    &lt;span data-hl=&quot;variable.builtin&quot;&gt;self&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;.&lt;/span&gt;&lt;span data-hl=&quot;function.method&quot;&gt;insert_block&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;(&lt;/span&gt;block_index&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;)&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;;&lt;/span&gt;
&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Once per frame we scan the super-blocks of all TLSF instances, immediately
releasing any which have become entirely unused. In a more production ready
setup this work would be amortized across a longer time period and avoid
blocking the frame.&lt;/p&gt;&lt;/section&gt;&lt;section id=&quot;thread-local-bump-allocator&quot;&gt;&lt;h1&gt;Thread Local Bump Allocator&lt;/h1&gt;&lt;p&gt;In order to reduce pressure on the general purpose allocator we implement a
double-buffered thread-local bump allocator for transient allocations which last
only a single frame. This allows the calling thread to avoid needing to take a
lock, and reduces the complex allocation bookkeeping to a few branches and a
subtraction.&lt;/p&gt;&lt;p&gt;If a transient buffer is requested that is beyond the block size for the
transient allocator, it will fall-back to the general allocator, and immediately
queue the buffer for deferred destruction.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-rust&quot;&gt;&lt;span data-hl=&quot;keyword&quot;&gt;let&lt;/span&gt; texture&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;:&lt;/span&gt; &lt;span data-hl=&quot;operator&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;[&lt;/span&gt;&lt;span data-hl=&quot;type.builtin&quot;&gt;u8&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;]&lt;/span&gt; = _&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;;&lt;/span&gt;

&lt;span data-hl=&quot;keyword&quot;&gt;let&lt;/span&gt; buffer = device&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;.&lt;/span&gt;&lt;span data-hl=&quot;function.method&quot;&gt;request_transient_buffer_with_data&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;(&lt;/span&gt;
    &lt;span data-hl=&quot;operator&quot;&gt;&amp;amp;&lt;/span&gt;frame&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;,&lt;/span&gt;
    &lt;span data-hl=&quot;operator&quot;&gt;&amp;amp;&lt;/span&gt;thread_token&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;,&lt;/span&gt;
    &lt;span data-hl=&quot;type&quot;&gt;BufferUsageFlags&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;::&lt;/span&gt;&lt;span data-hl=&quot;constructor&quot;&gt;TRANSFER&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;,&lt;/span&gt;
    texture&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;,&lt;/span&gt;
&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;)&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;;&lt;/span&gt;

device&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;.&lt;/span&gt;&lt;span data-hl=&quot;function.method&quot;&gt;cmd_copy_buffer_to_image&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;(&lt;/span&gt;
    &lt;span data-hl=&quot;operator&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span data-hl=&quot;keyword&quot;&gt;mut&lt;/span&gt; cmd_buffer&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;,&lt;/span&gt;
    buffer&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;.&lt;/span&gt;&lt;span data-hl=&quot;function.method&quot;&gt;into&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;(&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;)&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;,&lt;/span&gt;
    image&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;,&lt;/span&gt;
    &lt;span data-hl=&quot;type&quot;&gt;ImageLayout&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;::&lt;/span&gt;&lt;span data-hl=&quot;constructor&quot;&gt;Optimal&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;,&lt;/span&gt;
    &lt;span data-hl=&quot;operator&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;[&lt;/span&gt;&lt;span data-hl=&quot;type&quot;&gt;BufferImageCopy&lt;/span&gt; &lt;span data-hl=&quot;punctuation.bracket&quot;&gt;{&lt;/span&gt;
        &lt;span data-hl=&quot;property&quot;&gt;buffer_offset&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;:&lt;/span&gt; &lt;span data-hl=&quot;constant.builtin&quot;&gt;0&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;,&lt;/span&gt;
        &lt;span data-hl=&quot;property&quot;&gt;buffer_row_length&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;:&lt;/span&gt; &lt;span data-hl=&quot;constant.builtin&quot;&gt;0&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;,&lt;/span&gt;
        &lt;span data-hl=&quot;property&quot;&gt;buffer_image_height&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;:&lt;/span&gt; &lt;span data-hl=&quot;constant.builtin&quot;&gt;0&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;,&lt;/span&gt;
        &lt;span data-hl=&quot;property&quot;&gt;image_subresource&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;:&lt;/span&gt; &lt;span data-hl=&quot;function&quot;&gt;default&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;(&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;)&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;,&lt;/span&gt;
        &lt;span data-hl=&quot;property&quot;&gt;image_offset&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;:&lt;/span&gt; &lt;span data-hl=&quot;type&quot;&gt;Offset3d&lt;/span&gt; &lt;span data-hl=&quot;punctuation.bracket&quot;&gt;{&lt;/span&gt; &lt;span data-hl=&quot;property&quot;&gt;x&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;:&lt;/span&gt; &lt;span data-hl=&quot;constant.builtin&quot;&gt;0&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;,&lt;/span&gt; &lt;span data-hl=&quot;property&quot;&gt;y&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;:&lt;/span&gt; &lt;span data-hl=&quot;constant.builtin&quot;&gt;0&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;,&lt;/span&gt; &lt;span data-hl=&quot;property&quot;&gt;z&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;:&lt;/span&gt; &lt;span data-hl=&quot;constant.builtin&quot;&gt;0&lt;/span&gt; &lt;span data-hl=&quot;punctuation.bracket&quot;&gt;}&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;,&lt;/span&gt;
        &lt;span data-hl=&quot;property&quot;&gt;image_extent&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;:&lt;/span&gt; &lt;span data-hl=&quot;type&quot;&gt;Extent3d&lt;/span&gt; &lt;span data-hl=&quot;punctuation.bracket&quot;&gt;{&lt;/span&gt;
            width&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;,&lt;/span&gt;
            height&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;,&lt;/span&gt;
            &lt;span data-hl=&quot;property&quot;&gt;depth&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;:&lt;/span&gt; &lt;span data-hl=&quot;constant.builtin&quot;&gt;1&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;,&lt;/span&gt;
        &lt;span data-hl=&quot;punctuation.bracket&quot;&gt;}&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;,&lt;/span&gt;
    &lt;span data-hl=&quot;punctuation.bracket&quot;&gt;}&lt;/span&gt;&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;]&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;,&lt;/span&gt;
&lt;span data-hl=&quot;punctuation.bracket&quot;&gt;)&lt;/span&gt;&lt;span data-hl=&quot;punctuation.delimiter&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The &lt;code&gt;request_transient_buffer_with_data&lt;/code&gt; function returns a handle bound to the
lifetime of the &lt;code&gt;frame&lt;/code&gt; parameter which prevents the common error of persisting
references to frame allocated data after the frame has been submitted. At the
beginning of a frame the linear allocators are reset, and the underlying buffers
are recycled.&lt;/p&gt;&lt;p&gt;For this scheme to be production-ready it would also be important that the
system track the amount of memory used each frame, and release excess memory
that is unused over a longer period of time to the general allocator. As it is
currently implemented, any buffers allocated by the transient memory pools will
remain attached to those pools until shutdown.&lt;/p&gt;&lt;/section&gt;&lt;section id=&quot;zeroed-memory&quot;&gt;&lt;h1&gt;Zeroed Memory&lt;/h1&gt;&lt;p&gt;When memory is provided from the kernel to a desktop application, it’s
well-defined that the contents of that memory are zeroed. While the mechanics
differ between operating systems, this is safe for an application to rely on,
and is important for security as un-cleared memory from other processes could
contain secrets. With video memory allocations it’s… less straightforward.&lt;/p&gt;&lt;p&gt;On Windows, fresh video memory from the OS should always be zeroed. DX12
specifies that newly allocated video memory is always zeroed, and then provides
an opt-out via &lt;code&gt;D3D12_HEAP_FLAG_CREATE_NOT_ZEROED&lt;/code&gt;, where drivers may recycle
memory within a single process without clearing it. Practically speaking, this
means that Vulkan drivers on Windows also default to zeroing video memory before
handing it off to an application.&lt;/p&gt;&lt;p&gt;On Linux the situation is the opposite. Not only do drivers avoid clearing
memory within a process, but until recently amdgpu&lt;a id=&quot;fnref2&quot; href=&quot;#fn2&quot; role=&quot;doc-noteref&quot; style=&quot;--anchor-name: --fnref2&quot;&gt;&lt;sup&gt;2&lt;/sup&gt;&lt;/a&gt; did not even &lt;a href=&quot;https://lore.kernel.org/amd-gfx/da90e4c0-067b-2ffe-01df-f59c2b7ec556@amd.com/&quot;&gt;clear&lt;/a&gt;
&lt;a href=&quot;https://lore.kernel.org/amd-gfx/20240829172645.1678920-1-alexander.deucher@amd.com/&quot;&gt;video memory&lt;/a&gt;
from other processes. This means it’s vitally important that you manually clear
video memory after receiving it, where that would be important to your
application’s semantics. It’s a common enough source of problems that mesa has a
frequently applied &lt;a href=&quot;https://gitlab.freedesktop.org/mesa/mesa/-/blob/fe2c93a78893419bd90757c5ebaf17f33c05a976/src/util/driconf.h#L535-537&quot;&gt;driconf quirk&lt;/a&gt;
which forces the driver to zero allocated video memory.&lt;/p&gt;&lt;p&gt;To bring order to the chaos, &lt;code&gt;VK_EXT_zero_initialize_device_memory&lt;/code&gt; is a new
extension which introduces a flag, &lt;code&gt;VK_MEMORY_ALLOCATE_ZERO_INITIALIZE_BIT_EXT&lt;/code&gt;,
enabling applications to request the driver perform zero initialization on a
per-allocation basis. Though note the default is still backwards compared to
DX12 and Windows Vulkan.&lt;/p&gt;&lt;/section&gt;&lt;section role=&quot;doc-endnotes&quot;&gt;&lt;ol&gt;&lt;li id=&quot;fn1&quot; style=&quot;--anchor: --fnref1&quot;&gt;&lt;p&gt;This means that you have to throw a lock around all device
memory allocations.&lt;a href=&quot;#fnref1&quot; role=&quot;doc-backlink&quot; style=&quot;style=&quot;--anchor-name: --fnref1&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;&lt;li id=&quot;fn2&quot; style=&quot;--anchor: --fnref2&quot;&gt;&lt;p&gt;amdgpu is the kernel side vulkan driver for AMD GPUs on Linux.&lt;a href=&quot;#fnref2&quot; role=&quot;doc-backlink&quot; style=&quot;style=&quot;--anchor-name: --fnref2&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;&lt;/section&gt;</content>
</entry>
<entry>
<id>urn:uuid:019f1565-e2a4-7d4c-9dd7-9d87b23350bf</id>
<title>Hello, World!</title>
<author><name>Josh Simmons</name></author>
<updated>2025-06-02T00:00:00Z</updated>
<link href="https://nega.tv/posts/hello-world.html" rel="alternate" type="text/html" />
<content type="html">
&lt;section id=&quot;hello-world&quot;&gt;&lt;h1&gt;Hello, World!&lt;/h1&gt;&lt;/section&gt;</content>
</entry>
</feed>
