<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="/feed.xml" rel="self" type="application/atom+xml" /><link href="/" rel="alternate" type="text/html" /><updated>2026-05-27T19:44:07-05:00</updated><id>/feed.xml</id><title type="html">Hendo’s Blog</title><author><name>Hendo</name></author><entry><title type="html">Epidemiology</title><link href="/epidemilogy/" rel="alternate" type="text/html" title="Epidemiology" /><published>2026-05-17T00:00:00-05:00</published><updated>2026-05-17T00:00:00-05:00</updated><id>/epidemilogy</id><content type="html" xml:base="/epidemilogy/"><![CDATA[<blockquote>
<p>Epidemiology is a serious subject and one I&#39;m not really an expert in. So while this is a fun experiment don&#39;t treat it as a serious public health thingy.</p>
</blockquote>

<p>I was playing the board game Twilight Imperium and came across an interesting scenario for analysis and modelling infections. It&#39;s complex board game, with a large hexagonal map:</p>

<p><img src="/assets/img/epidemiology/board.jpg" alt=""></p>

<p>The map is made up of differing systems, each represented by a hexagon, which may contain planets. Different systems have differing planets, some with up to 3, some with none. One of the game&#39;s central mechanics is occupying and controlling these systems by placing &quot;infantry&quot; on them, representing populations<label for='JMPpz' class='margin-toggle sidenote-number'></label><input type='checkbox' id='JMPpz' class='margin-toggle'/><span class='sidenote'>There are no civilian populations in the game, just military </span>. As the game progresses, players spread out from home systems, pushing through unocupied planets, eventually colliding into skirmishes and forming frontlines.</p>

<p><img src="/assets/img/epidemiology/supersimple.png" alt=""></p>

<blockquote>
<p>Systems from left to right, 3 Red infantry, 1 Blue and 1 Green infantry, no infantry</p>
</blockquote>

<p>In a recent game a particular action card came up, <em>Locust</em>:</p>

<div style="text-align: center;">
<img src="/assets/img/epidemiology/locust.jpg" width=400px>
</div>

<!-- <figure><img src='/assets/img/epidemiology/locust.jpg'/><figcaption class='maincolumn-figure'>The player chooses a start point for the infection, then steers it as it moves.</figcaption></figure> -->

<p>The idea is to unleash a targeted plague on the galaxy, aimed at your opponents systems. But it depends partially on chance, the roll of a dice, to determine its spread. So in some ways, this might behave like a real infection. I thought it would be fun to treat this as an experiment in statistical modelling, and see if we can predict the spread of the plague.</p>

<hr>

<p>Now if you think about it, this is not a disease, and adheres to the card&#39;s name: it simulates a single &quot;plague&quot; of locusts moving on its own path, destroying things in it&#39;s path. by contrast, diseases spread in all directions, multiplying and re-infecting, leading to much greater infection:</p>

<p><img src="/assets/img/epidemiology/vs.png" alt=""></p>

<p>Nonetheless, in this scenario the locusts spread through connected populations and therefore behave somewhat like a communicable disease, so we&#39;re going to continue using terminology &quot;epidemic&quot;, &quot;infection&quot;, etc.</p>

<!-- <div class='epigraph'><blockquote><p>An epidemic (from Greek ἐπί epi "upon or above" and δῆμος demos "people") is the rapid spread of disease to a large number of hosts in a given population within a short period of time</p><footer>George Orwell, <cite>https://en.wikipedia.org/wiki/Epidemic</cite></footer></blockquote></div> -->

<h2 id="real-epidemiology">Real epidemiology</h2>

<p>In the real world analysis and depiction of diseases is a much more complex topic than in our example. In real life analysis has different purposes:<label for='x0CZx' class='margin-toggle sidenote-number'></label><input type='checkbox' id='x0CZx' class='margin-toggle'/><span class='sidenote'>there are probably official terms for these </span></p>

<ul>
<li>Retro-active analysis: looking backwards to ascertain truth, where an infection started, or what&#39;s causing it</li>
<li>Trying to analyse behaviour of disease (how it&#39;s transmitted)</li>
<li>Future facing analysis: trying to predict future infections and stop them</li>
</ul>

<p>Retro-active analysis of an epidemic was the function of one of the most famous statistical illustration out there: John Snow&#39;s Cholera Map of London.<label for='b9qgG' class='margin-toggle'> &#8853;</label><input type='checkbox' id='b9qgG' class='margin-toggle'/><span class='marginnote'></p>

<p>The original can be found here: Project Gutenberg - On the Mode of Communication of Cholera <a href="https://www.gutenberg.org/files/72894/72894-h/72894-h.htm">https://www.gutenberg.org/files/72894/72894-h/72894-h.htm</a> </span></p>

<div align="center">

<img src="/assets/img/epidemiology/snow1.jpg" width=600px>

</div>

<blockquote>
<p><em>John Snow</em>, A Geography of Life and Death</p>
</blockquote>

<p>Snow recorded deaths in London during a cholera pandemic, shown here by dots, and when viewed on a map, their clustering identified the broad street pump as the source of infection. When the pump was closed, this reduced rates of infection in the area<label for='pkpAt' class='margin-toggle sidenote-number'></label><input type='checkbox' id='pkpAt' class='margin-toggle'/><span class='sidenote'><a href="https://www.londonmuseum.org.uk/collections/london-stories/john-snow-cholera-broad-street-pump/">https://www.londonmuseum.org.uk/collections/london-stories/john-snow-cholera-broad-street-pump/</a> </span>. This nicely showcases the power of 
effective illustration in communicating data, and is a nice prelude to our example.</p>

<p>Real epidemiology is further complicated by the fact that the data itself is uncertain. Estimates for &quot;transmissibility&quot; vary, and are often revised retroactively. Infections and casualties may be over or under diagnosed, mis-attributed, and take time to analyse. Different people and populations have different susceptibilities to diseases, depending on age, genetics, access to medicine, climate, population density, etc. </p>

<p>There&#39;s a great chapter discussing these complexities in &quot;The Signal and the Noise&quot;<label for='qx6kx' class='margin-toggle sidenote-number'></label><input type='checkbox' id='qx6kx' class='margin-toggle'/><span class='sidenote'><a href="https://www.amazon.co.uk/Signal-Noise-Art-Science-Prediction/dp/0141975652">https://www.amazon.co.uk/Signal-Noise-Art-Science-Prediction/dp/0141975652</a> </span>. This discusses challenges in predicting epidemics, and how prediction / reporting of a disease is itself a variable in their spread.</p>

<p>In the scenario we&#39;ll be looking at we have a well defined population, and a perfect definition of how the infection spreads which remains constant across every transmission, greatly simplifying our model.</p>

<!-- Both the UK and US governments rank the threat of epidemics as the highest threat to their national security, as we  -->

<h2 id="plan">Plan</h2>

<p>Anyway, let&#39;s go back to the game scenario. We want to model and illustrate the possible outcomes of the infection, and to do this we need two input variables:</p>

<ul>
<li>Starting state: population of the map before the disease</li>
<li>Spreading behaviour</li>
</ul>

<p>Our output will be a map of expected casualties.</p>

<h2 id="model">Model</h2>

<p>In epidemiology, transmisibility of a disease is expressed as an &#39;R-number&#39;<label for='MmmUH' class='margin-toggle sidenote-number'></label><input type='checkbox' id='MmmUH' class='margin-toggle'/><span class='sidenote'><a href="https://en.wikipedia.org/wiki/Basic_reproduction_number">https://en.wikipedia.org/wiki/Basic_reproduction_number</a> </span>. This represents the number of people infected by an existing infection, and determines the rate of spread of the disease. Obviously factors like immunity and variations affect the real numbers, but the nice thing about large populations is that they tend to obey statistical laws at a macro level. </p>

<p>In our game the card says there is a constant chance of infection from one system to the next, and that the locusts can travel to &quot;the same or adjacent&quot; system, so systems can be re-infected<label for='4w5i2' class='margin-toggle sidenote-number'></label><input type='checkbox' id='4w5i2' class='margin-toggle'/><span class='sidenote'>The infection doesn&#39;t care about the number of planets, just systems and infantry. </span>. </p>

<p>In reality the player playing the card has agency and can choose which system to target. They&#39;re likely to target it at systems with larger numbers of infantry, or protecting more strategic locations. We <em>could</em> attempt to model this by giving a greater likelihood to systems with higher populations, but then we&#39;re really just introducing more bias into the model. </p>

<p>So I&#39;ll go with the simplest possible model, where every adjacent system, plus the original system, has an equal likelihood of infection.</p>

<div style="text-align: center;">
<img src="/assets/img/epidemiology/simple.png" width=600px>
</div>

<p>The chain of infections ends when it fails to infect, which is when the player rolls &lt; 3. The game uses a 10 sided dice and 0&#39;s count as 10, so there is an 0.8 chance of the infection continuing, and 0.2 of it stopping.</p>
<div class="highlight"><pre><code class="language-" data-lang="">            1 2 = Infection stops
3 4 5 6 7 8 9 0 = Infection continues 

p = 0.2
q = 0.8
</code></pre></div>
<p>Each infection attempt is therefore a boolean variable, and we can model it&#39;s spread as a <a href="https://en.wikipedia.org/wiki/Geometric_distribution">Geometric distribution</a> with probability of infection (p) = 0.8, and the number of systems infected in the chain is <code>k</code>. This is because successes have to be successive. If we relate this to coin flipping, we are measuring the number of times you get heads in a row, not the number of heads you get when flipping a coin 10 times. That would be described by a <a href="https://en.wikipedia.org/wiki/Binomial_distribution">Binomial Distribution</a>.</p>

<p>We can see the expected spread in the table and graph below.</p>

<p>The final column of the table is probably what we&#39;re interested in, this is the appropriately named <a href="https://en.wikipedia.org/wiki/Survival_function">Survival function</a>, P(X≥k)=p, and is equivalent to &quot;what&#39;s the probability the infection gets to <em>at least</em> this number of infections before ending&quot;.</p>

<table><thead>
<tr>
<th>k (successes)</th>
<th>calculation</th>
<th>P(X = k)</th>
<th>P(X ≥ k)</th>
</tr>
</thead><tbody>
<tr>
<td>0</td>
<td>0.2</td>
<td>0.2000</td>
<td>1.0000</td>
</tr>
<tr>
<td>1</td>
<td>0.8 × 0.2</td>
<td>0.1600</td>
<td>0.8000</td>
</tr>
<tr>
<td>2</td>
<td>0.8² × 0.2</td>
<td>0.1280</td>
<td>0.6400</td>
</tr>
<tr>
<td>3</td>
<td>0.8³ × 0.2</td>
<td>0.1024</td>
<td>0.5120</td>
</tr>
<tr>
<td>4</td>
<td>0.8⁴ × 0.2</td>
<td>0.0819</td>
<td>0.4096</td>
</tr>
<tr>
<td>5</td>
<td>0.8⁵ × 0.2</td>
<td>0.0655</td>
<td>0.3277</td>
</tr>
<tr>
<td>6</td>
<td>0.8⁶ × 0.2</td>
<td>0.0524</td>
<td>0.2621</td>
</tr>
<tr>
<td>7</td>
<td>0.8⁷ × 0.2</td>
<td>0.0419</td>
<td>0.2097</td>
</tr>
<tr>
<td>8</td>
<td>0.8⁸ × 0.2</td>
<td>0.0336</td>
<td>0.1678</td>
</tr>
<tr>
<td>9</td>
<td>0.8⁹ × 0.2</td>
<td>0.0268</td>
<td>0.1342</td>
</tr>
<tr>
<td>10</td>
<td>0.8¹⁰ × 0.2</td>
<td>0.0214</td>
<td>0.1074</td>
</tr>
</tbody></table>

<div class="dropdown-code">
  <button class="dropdown-code-toggle" onclick="toggleDropdownCode('dropdown-13496')" aria-expanded="false">
    <span class="dropdown-arrow">▶</span>
    <span class="dropdown-title">survival.py</span>
  </button>
  <div id="dropdown-13496" class="dropdown-code-content" hidden>
    <div class="highlight"><pre><code class="language-python" data-lang="python"><span class="kn">import</span> <span class="nn">matplotlib.pyplot</span> <span class="k">as</span> <span class="n">plt</span>
<span class="kn">import</span> <span class="nn">numpy</span> <span class="k">as</span> <span class="n">np</span>

<span class="n">p</span> <span class="o">=</span> <span class="mf">0.8</span>
<span class="n">q</span> <span class="o">=</span> <span class="mi">1</span> <span class="o">-</span> <span class="n">p</span>
<span class="n">k</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="n">arange</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">11</span><span class="p">)</span>
<span class="n">probs</span> <span class="o">=</span> <span class="p">(</span><span class="n">p</span> <span class="o">**</span> <span class="n">k</span><span class="p">)</span> <span class="o">*</span> <span class="n">q</span>
<span class="n">survival</span> <span class="o">=</span> <span class="n">p</span> <span class="o">**</span> <span class="n">k</span>

<span class="n">fig</span><span class="p">,</span> <span class="n">ax1</span> <span class="o">=</span> <span class="n">plt</span><span class="p">.</span><span class="n">subplots</span><span class="p">(</span><span class="n">figsize</span><span class="o">=</span><span class="p">(</span><span class="mi">10</span><span class="p">,</span> <span class="mi">6</span><span class="p">))</span>

<span class="n">ax1</span><span class="p">.</span><span class="n">set_ylim</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span>


<span class="n">fig</span><span class="p">.</span><span class="n">patch</span><span class="p">.</span><span class="n">set_facecolor</span><span class="p">(</span><span class="s">'none'</span><span class="p">)</span>
<span class="n">ax1</span><span class="p">.</span><span class="n">patch</span><span class="p">.</span><span class="n">set_facecolor</span><span class="p">(</span><span class="s">'none'</span><span class="p">)</span>


<span class="c1"># bars for P(X = k)
</span><span class="n">bars</span> <span class="o">=</span> <span class="n">ax1</span><span class="p">.</span><span class="n">bar</span><span class="p">(</span><span class="n">k</span><span class="p">,</span> <span class="n">probs</span><span class="p">,</span> <span class="n">color</span><span class="o">=</span><span class="s">'steelblue'</span><span class="p">,</span> <span class="n">edgecolor</span><span class="o">=</span><span class="s">'black'</span><span class="p">,</span> <span class="n">alpha</span><span class="o">=</span><span class="mf">0.7</span><span class="p">,</span> <span class="n">label</span><span class="o">=</span><span class="s">'P(X = k)'</span><span class="p">)</span>
<span class="n">ax1</span><span class="p">.</span><span class="n">set_xlabel</span><span class="p">(</span><span class="s">'k (successes before failure)'</span><span class="p">)</span>
<span class="n">ax1</span><span class="p">.</span><span class="n">set_ylabel</span><span class="p">(</span><span class="s">'P(X = k)'</span><span class="p">,</span> <span class="n">color</span><span class="o">=</span><span class="s">'steelblue'</span><span class="p">)</span>
<span class="n">ax1</span><span class="p">.</span><span class="n">tick_params</span><span class="p">(</span><span class="n">axis</span><span class="o">=</span><span class="s">'y'</span><span class="p">,</span> <span class="n">labelcolor</span><span class="o">=</span><span class="s">'steelblue'</span><span class="p">)</span>
<span class="n">ax1</span><span class="p">.</span><span class="n">set_xticks</span><span class="p">(</span><span class="n">k</span><span class="p">)</span>

<span class="k">for</span> <span class="n">i</span><span class="p">,</span> <span class="n">prob</span> <span class="ow">in</span> <span class="nb">enumerate</span><span class="p">(</span><span class="n">probs</span><span class="p">):</span>
    <span class="n">ax1</span><span class="p">.</span><span class="n">text</span><span class="p">(</span><span class="n">i</span><span class="p">,</span> <span class="n">prob</span> <span class="o">+</span> <span class="mf">0.002</span><span class="p">,</span> <span class="sa">f</span><span class="s">'</span><span class="si">{</span><span class="n">prob</span><span class="si">:</span><span class="p">.</span><span class="mi">4</span><span class="n">f</span><span class="si">}</span><span class="s">'</span><span class="p">,</span> <span class="n">ha</span><span class="o">=</span><span class="s">'center'</span><span class="p">,</span> <span class="n">va</span><span class="o">=</span><span class="s">'bottom'</span><span class="p">,</span> <span class="n">fontsize</span><span class="o">=</span><span class="mi">7</span><span class="p">,</span> <span class="n">color</span><span class="o">=</span><span class="s">'steelblue'</span><span class="p">)</span>

<span class="c1"># survival function on second y axis
</span><span class="n">ax2</span> <span class="o">=</span> <span class="n">ax1</span><span class="p">.</span><span class="n">twinx</span><span class="p">()</span>
<span class="n">ax2</span><span class="p">.</span><span class="n">plot</span><span class="p">(</span><span class="n">k</span><span class="p">,</span> <span class="n">survival</span><span class="p">,</span> <span class="n">color</span><span class="o">=</span><span class="s">'crimson'</span><span class="p">,</span> <span class="n">marker</span><span class="o">=</span><span class="s">'o'</span><span class="p">,</span> <span class="n">linewidth</span><span class="o">=</span><span class="mi">2</span><span class="p">,</span> <span class="n">label</span><span class="o">=</span><span class="s">'P(X ≥ k)'</span><span class="p">)</span>
<span class="n">ax2</span><span class="p">.</span><span class="n">set_ylabel</span><span class="p">(</span><span class="s">'P(X ≥ k)'</span><span class="p">,</span> <span class="n">color</span><span class="o">=</span><span class="s">'crimson'</span><span class="p">)</span>
<span class="n">ax2</span><span class="p">.</span><span class="n">tick_params</span><span class="p">(</span><span class="n">axis</span><span class="o">=</span><span class="s">'y'</span><span class="p">,</span> <span class="n">labelcolor</span><span class="o">=</span><span class="s">'crimson'</span><span class="p">)</span>
<span class="n">ax2</span><span class="p">.</span><span class="n">set_ylim</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span>
<span class="n">ax2</span><span class="p">.</span><span class="n">patch</span><span class="p">.</span><span class="n">set_facecolor</span><span class="p">(</span><span class="s">'none'</span><span class="p">)</span>

<span class="k">for</span> <span class="n">i</span><span class="p">,</span> <span class="n">s</span> <span class="ow">in</span> <span class="nb">enumerate</span><span class="p">(</span><span class="n">survival</span><span class="p">):</span>
    <span class="n">ax2</span><span class="p">.</span><span class="n">text</span><span class="p">(</span><span class="n">i</span><span class="p">,</span> <span class="n">s</span> <span class="o">+</span> <span class="mf">0.01</span><span class="p">,</span> <span class="sa">f</span><span class="s">'</span><span class="si">{</span><span class="n">s</span><span class="si">:</span><span class="p">.</span><span class="mi">4</span><span class="n">f</span><span class="si">}</span><span class="s">'</span><span class="p">,</span> <span class="n">ha</span><span class="o">=</span><span class="s">'center'</span><span class="p">,</span> <span class="n">va</span><span class="o">=</span><span class="s">'bottom'</span><span class="p">,</span> <span class="n">fontsize</span><span class="o">=</span><span class="mi">7</span><span class="p">,</span> <span class="n">color</span><span class="o">=</span><span class="s">'crimson'</span><span class="p">)</span>

<span class="c1"># combined legend
</span><span class="n">lines1</span><span class="p">,</span> <span class="n">labels1</span> <span class="o">=</span> <span class="n">ax1</span><span class="p">.</span><span class="n">get_legend_handles_labels</span><span class="p">()</span>
<span class="n">lines2</span><span class="p">,</span> <span class="n">labels2</span> <span class="o">=</span> <span class="n">ax2</span><span class="p">.</span><span class="n">get_legend_handles_labels</span><span class="p">()</span>
<span class="n">ax1</span><span class="p">.</span><span class="n">legend</span><span class="p">(</span><span class="n">lines1</span> <span class="o">+</span> <span class="n">lines2</span><span class="p">,</span> <span class="n">labels1</span> <span class="o">+</span> <span class="n">labels2</span><span class="p">,</span> <span class="n">loc</span><span class="o">=</span><span class="s">'upper right'</span><span class="p">)</span>

<span class="n">plt</span><span class="p">.</span><span class="n">title</span><span class="p">(</span><span class="sa">f</span><span class="s">'Geometric Distribution (p = </span><span class="si">{</span><span class="n">p</span><span class="si">}</span><span class="s">)'</span><span class="p">)</span>
<span class="n">fig</span><span class="p">.</span><span class="n">tight_layout</span><span class="p">()</span>

<span class="c1"># save as html via mpld3
</span><span class="k">try</span><span class="p">:</span>
    <span class="kn">import</span> <span class="nn">mpld3</span>
    <span class="n">html</span> <span class="o">=</span> <span class="n">mpld3</span><span class="p">.</span><span class="n">fig_to_html</span><span class="p">(</span><span class="n">fig</span><span class="p">)</span>
    <span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="s">'geometric_distribution.html'</span><span class="p">,</span> <span class="s">'w'</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
        <span class="n">f</span><span class="p">.</span><span class="n">write</span><span class="p">(</span><span class="n">html</span><span class="p">)</span>
    <span class="k">print</span><span class="p">(</span><span class="s">'[+] saved to geometric_distribution.html'</span><span class="p">)</span>
<span class="k">except</span> <span class="nb">ImportError</span><span class="p">:</span>
    <span class="k">print</span><span class="p">(</span><span class="s">'[-] mpld3 not found, install with: pip install mpld3'</span><span class="p">)</span>
    <span class="k">print</span><span class="p">(</span><span class="s">'[-] falling back to png'</span><span class="p">)</span>
    <span class="n">plt</span><span class="p">.</span><span class="n">savefig</span><span class="p">(</span><span class="s">'geometric_distribution.png'</span><span class="p">,</span> <span class="n">dpi</span><span class="o">=</span><span class="mi">150</span><span class="p">,</span> <span class="n">transparent</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span>

<span class="n">plt</span><span class="p">.</span><span class="n">show</span><span class="p">()</span>
</code></pre></div>
  </div>
</div>

<div style="text-align: center;">
<iframe src="/assets/img/epidemiology/geometric_distribution.html" width="1010" height="620" style="border:none; background:transparent;" frameborder="0"></iframe>
</div>

<p>As we can see, the geometric distribution means it trails off. If the infection travels in a straight line from the centre, we can map it&#39;s expected reach as a heatmap. </p>

<div style="text-align: center;">
<iframe src="/assets/img/epidemiology/hex_grid_infection_heatmap.html" width="850" height="710" style="border:none; background:transparent;" frameborder="0"></iframe>
</div>

<p>But in reality the process is more involved. The infection can change direction, and not every system can be infected. Additionally </p>

<p>The system of actions is:</p>

<ul>
<li>System is infected</li>
<li>10 sided dice is rolled<br></li>
<li>If result =&lt; 2, the infection halts<label for='NuFEG' class='margin-toggle'> &#8853;</label><input type='checkbox' id='NuFEG' class='margin-toggle'/><span class='marginnote'>remmeber 0 = 10 in this system </span></li>
<li>If result &gt;=3 an infantry is killed</li>
<li>The infection moves to the same or adjacent system and repeats</li>
</ul>

<p>This means, if a system has 1 infantry, and it&#39;s destroyed, it can&#39;t be re-infected. This makes the simulation more complex and means we probably can&#39;t be simulated with a simple geometric distribution.</p>

<p>Instead, the best way to model this will be a <a href="https://en.wikipedia.org/wiki/Monte_Carlo_method">Monte-carlo simulation</a>, in which we simulate potential outcomes and record the results.</p>

<p>To do this I&#39;ll build a model of the galaxy, simulate an infection until it peters out, reaching an eventual end-state with casualties. Then I&#39;ll run a number of simulations, and average the results, modelling expected outcomes and the average fatalities per system.</p>

<h3 id="building-a-model">Building a model</h3>

<p>All we need is an object for each system, with a population count (for each player), and list of adjacent systems.</p>

<p>Then we configure the model with the number of infantry in each system. I did this from a snapshot of the game state, and a python script to parse the populations.  Shown below, the populations turn out to be very sparse, significantly reducing the likelihood of a significant epidemic.</p>

<div style="text-align: center;">
<iframe src="/assets/img/epidemiology/population_map.html" width="900" height="700" style="border:none; background:transparent;" frameborder="0" margin-bottom="0"></iframe>
</div>

<h3 id="start-state">Start state</h3>

<p>The originating player can choose any system adjacent to theirs in which to begin the infection. We could assume an equal likelihood amongst adjacent systems:</p>

<div style="text-align: center;">
<img src="/assets/img/epidemiology/start_state.png" width=600px>
</div>

<p>Or we could simulate from the point at which the player has chosen the first system to infect. This is the easiest option so is what we&#39;ll go with. The system chosen in the game was <code>(-1,-1)</code> on the above map, the tile with 2 orange population, below left of the centre.</p>

<h3 id="transmission">Transmission</h3>

<p>Now we model an infection. This is nice and simple based on the list of adjacent systems and their per-player populations. Our starting point will be </p>

<p><label for='ObdU6' class='margin-toggle sidenote-number'></label><input type='checkbox' id='ObdU6' class='margin-toggle'/><span class='sidenote'>Technically, the player <em>could</em> deliberately target their own systems, and might if it provided a bridge to a particularly high value target </span></p>

<p><label for='PIQni' class='margin-toggle'>&#8853;</label><input type='checkbox' id='PIQni' class='margin-toggle'/><span class='marginnote'><img class='fullwidth' src='/assets/img/epidemiology/reinfect.png'/><br></span></p>
<div class="highlight"><pre><code class="language-python" data-lang="python"><span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">max_steps</span><span class="p">):</span>
    <span class="n">roll</span> <span class="o">=</span> <span class="n">roll_d10</span><span class="p">()</span>

    <span class="k">if</span> <span class="n">roll</span> <span class="o">&lt;=</span> <span class="mi">2</span><span class="p">:</span>
        <span class="c1"># infection halts
</span>        <span class="k">break</span>

    <span class="c1"># roll &gt;= 3: kill one unit in current system
</span>    <span class="c1"># pick a random player with population &gt; 0
</span>    <span class="n">mortal_players</span> <span class="o">=</span> <span class="p">[</span>
        <span class="n">p</span> <span class="k">for</span> <span class="n">p</span> <span class="ow">in</span> <span class="n">PLAYERS</span>
        <span class="k">if</span> <span class="n">p</span> <span class="o">!=</span> <span class="n">IMMUNE_PLAYER</span> <span class="ow">and</span> <span class="n">populations</span><span class="p">[</span><span class="n">current</span><span class="p">].</span><span class="n">get</span><span class="p">(</span><span class="n">p</span><span class="p">,</span> <span class="mi">0</span><span class="p">)</span> <span class="o">&gt;</span> <span class="mi">0</span>
    <span class="p">]</span>

    <span class="n">victim</span> <span class="o">=</span> <span class="n">random</span><span class="p">.</span><span class="n">choice</span><span class="p">(</span><span class="n">mortal_players</span><span class="p">)</span>
    <span class="n">populations</span><span class="p">[</span><span class="n">current</span><span class="p">][</span><span class="n">victim</span><span class="p">]</span> <span class="o">-=</span> <span class="mi">1</span>
    <span class="n">casualties</span><span class="p">[</span><span class="n">current</span><span class="p">][</span><span class="n">victim</span><span class="p">]</span> <span class="o">+=</span> <span class="mi">1</span>

    <span class="c1"># check if hex is now cleared
</span>    <span class="k">if</span> <span class="n">mortal_population</span><span class="p">(</span><span class="n">populations</span><span class="p">[</span><span class="n">current</span><span class="p">])</span> <span class="o">==</span> <span class="mi">0</span><span class="p">:</span>
        <span class="n">cleared</span><span class="p">.</span><span class="n">add</span><span class="p">(</span><span class="n">current</span><span class="p">)</span>

    <span class="c1"># find valid next systems
</span>    <span class="n">candidates</span> <span class="o">=</span> <span class="n">infectable_candidates</span><span class="p">(</span><span class="n">current</span><span class="p">,</span> <span class="n">populations</span><span class="p">,</span> <span class="n">cleared</span><span class="p">)</span>

    <span class="k">if</span> <span class="ow">not</span> <span class="n">candidates</span><span class="p">:</span>
        <span class="c1"># nowhere left to spread - halt
</span>        <span class="k">break</span>

    <span class="n">current</span> <span class="o">=</span> <span class="n">random</span><span class="p">.</span><span class="n">choice</span><span class="p">(</span><span class="n">candidates</span><span class="p">)</span>
</code></pre></div>
<p>You can find the complete script here: <a href="/assets/img/epidemiology/simulation.py">simulation.py</a></p>

<h3 id="results">Results</h3>

<p>So what happens when we run our simulation?</p>

<p>I set the number of experiments at 500 simulations. Here we can see the first 9, showing the variation. In 2 of them the infection fails to inflict any casualties<label for='1tzVk' class='margin-toggle sidenote-number'></label><input type='checkbox' id='1tzVk' class='margin-toggle'/><span class='sidenote'>This fits nicely with our <code>q = 0.2</code> </span>, and in a couple the infection goes up and down the entire galaxy. This nicely displays the varaition in results we can inspect</p>

<hr>

<style>
  .run-grid { display:grid; grid-template-columns:repeat(3,1fr); gap:8px; margin:12px 0; }
  .run-cell { border:0.5px solid rgba(0,0,0,0.15); border-radius:8px; overflow:hidden; background:#fff; }
  .run-label { font-size:10px; color:#888; padding:3px 8px; background:#fafaf8; border-bottom:0.5px solid rgba(0,0,0,0.08); }
  .run-wrap { position:relative; width:100%; height:250px; overflow:hidden; }
  .run-wrap iframe { width:300%; height:300%; border:none; transform:scale(0.3); transform-origin:top left; display:block; pointer-events:none; }
</style>

<div class="run-grid">
  <div class="run-cell"><div class="run-label">Run 1</div><div class="run-wrap"><iframe src="/assets/img/epidemiology/runs/run_0001.html" loading="lazy"></iframe></div></div>
  <div class="run-cell"><div class="run-label">Run 2</div><div class="run-wrap"><iframe src="/assets/img/epidemiology/runs/run_0002.html" loading="lazy"></iframe></div></div>
  <div class="run-cell"><div class="run-label">Run 3</div><div class="run-wrap"><iframe src="/assets/img/epidemiology/runs/run_0003.html" loading="lazy"></iframe></div></div>
  <div class="run-cell"><div class="run-label">Run 4</div><div class="run-wrap"><iframe src="/assets/img/epidemiology/runs/run_0004.html" loading="lazy"></iframe></div></div>
  <div class="run-cell"><div class="run-label">Run 5</div><div class="run-wrap"><iframe src="/assets/img/epidemiology/runs/run_0005.html" loading="lazy"></iframe></div></div>
  <div class="run-cell"><div class="run-label">Run 6</div><div class="run-wrap"><iframe src="/assets/img/epidemiology/runs/run_0006.html" loading="lazy"></iframe></div></div>
  <div class="run-cell"><div class="run-label">Run 7</div><div class="run-wrap"><iframe src="/assets/img/epidemiology/runs/run_0007.html" loading="lazy"></iframe></div></div>
  <div class="run-cell"><div class="run-label">Run 8</div><div class="run-wrap"><iframe src="/assets/img/epidemiology/runs/run_0008.html" loading="lazy"></iframe></div></div>
  <div class="run-cell"><div class="run-label">Run 9</div><div class="run-wrap"><iframe src="/assets/img/epidemiology/runs/run_0009.html" loading="lazy"></iframe></div></div>
</div>

<p>And, after 500 simulations, here&#39;s the map of average casualties per system, across all runs<label for='JLzjm' class='margin-toggle sidenote-number'></label><input type='checkbox' id='JLzjm' class='margin-toggle'/><span class='sidenote'>results <0.1 are hidden </span>:</p>

<div style="text-align: center;">
<iframe src="/assets/img/epidemiology/average_casualties.html" width="900" height="800" style="border:none; background:transparent;" frameborder="0" margin-bottom="0"></iframe>
</div>

<p>The mean casualties by player outcomes make sense:</p>
<div class="highlight"><pre><code class="language-" data-lang="">Mean casualties by player:
  green       :     0.73
  orange      :     1.87
  pink        :     0.00 [IMMUNE]
  purple      :     0.91
  red         :     0.05
  yellow      :     0.00
</code></pre></div>
<ul>
<li><p>Yellow is unaffected: all of yellow&#39;s populations are disconnected from the main system cluster<label for='OwEhD' class='margin-toggle'>&#8853;</label><input type='checkbox' id='OwEhD' class='margin-toggle'/><span class='marginnote'><img class='fullwidth' src='/assets/img/epidemiology/yellow.png'/><br>Yellow%26s nice little quarantine zone</span></p></li>
<li><p>Red is also largely unaffected: they only have 1 system reachable from the infection site, and it&#39;s 4 hops away<label for='GC46i' class='margin-toggle sidenote-number'></label><input type='checkbox' id='GC46i' class='margin-toggle'/><span class='sidenote'>Remembering our above geometric distribution earlier, the infection only has a 40% chance of making it 4 tiles, and will only go this way in a small number of simulations </span></p></li>
<li><p>Orange is the most heavily affected. They have the most population to start with by far, and the infection starts in a system with 2 of theirs. The only thing limiting the spread is that orange&#39;s most populous systems are not directly connected to the infection site. The weird isolated 0.1 in <code>(-3,3)</code> is presumably because that system had a total of 9 population, increasing the chance the disease re-infects that system.</p></li>
<li><p>Purple and green are roughly equivalent, but purple is closer to the initial infection site, hence higher casualty count.</p></li>
</ul>

<p>Overall the mean number of casualties was around 3, and in the majority of runs a maximum of 8 were inflicted, although across all 500 runs one simulation resulted in 18.</p>
<div class="highlight"><pre><code class="language-" data-lang="">Casualties per run:
  mean : 3.6
  min  : 0
  max  : 18
  p10  : 0
  p90  : 8
</code></pre></div>
<h3 id="reality">Reality</h3>

<p>In reality the infection successfully hit system <code>(-1,1)</code>, killing 1 infantry, and then moving to <code>(0,1)</code>, where a 2 was rolled, and the infection stopped. This matches our expected results exactly, if we convert probabilities to integers.</p>

<h2 id="curve-exponential-ai">Curve + exponential AI</h2>

<p>This is a slight tangent to the original subject, but ties in ideas of prediction, data graphics and epidemics.</p>

<p>This article was written around the time of the &quot;Mythos hypecycle&quot;, Anthropics teasing of their &quot;Mythos&quot; LLM model and some fairly inaccurate analysis that&#39;s been reported. Anthropic, and many others, have been making this out to be a step change in capability, in fact, so insane at finding vulnerabilities that it is just <em>too dangerous</em> to be released<label for='McKIb' class='margin-toggle sidenote-number'></label><input type='checkbox' id='McKIb' class='margin-toggle'/><span class='sidenote'>as opposed to maybe, far too expensive to run or buy </span>. This is pretty much marketing BS. It is a step up, and in some ways significantly so, but not what&#39;s being made out.</p>

<p>The team at Mozilla wrote a good article, and reported that they were able to use Mythos to find a substantial number of bugs across their systems<label for='iIJ2j' class='margin-toggle sidenote-number'></label><input type='checkbox' id='iIJ2j' class='margin-toggle'/><span class='sidenote'><a href="https://hacks.mozilla.org/2026/05/behind-the-scenes-hardening-firefox">https://hacks.mozilla.org/2026/05/behind-the-scenes-hardening-firefox</a> </span>. However, exclusively attributing this to the performance of Mythos would be a mistake:</p>

<ul>
<li>The Mozilla fuzzing team are world experts in fuzzing, and have clearly built an excellent bug hunting pipeline</li>
<li>In fact, they state that their harness performs almost as well with other models</li>
</ul>

<p>There&#39;s a great statistical analysis here: <a href="https://pointestimate.substack.com/p/how-good-is-mythos">https://pointestimate.substack.com/p/how-good-is-mythos</a>, by far the most rational thing I&#39;ve read written about this subject. It presents lots of graphs side by side, commenting on the trends, the error bars, and the reliability of the metrics<label for='9DbbE' class='margin-toggle sidenote-number'></label><input type='checkbox' id='9DbbE' class='margin-toggle'/><span class='sidenote'>I like this article, but its provenance is a bit odd, the substack has only a single post, and no linked identity etc. Absolutely doesn&#39;t mean it cant be trusted, but there&#39;s no historic record of other analyses / opinions to read up on to understand the source and its potential biases </span>.</p>

<p>That article analyses outcomes from the UK&#39;s <a href="https://www.aisi.gov.uk/">AI Security Institute (AISI)</a> research<label for='5JM64' class='margin-toggle sidenote-number'></label><input type='checkbox' id='5JM64' class='margin-toggle'/><span class='sidenote'><a href="https://www.aisi.gov.uk/blog/our-evaluation-of-claude-mythos-previews-cyber-capabilities">https://www.aisi.gov.uk/blog/our-evaluation-of-claude-mythos-previews-cyber-capabilities</a> </span>, one of the more reputable bodies in LLM analysis. Almost everyone else on the internet is just losing their heads and spouting nonsense. I&#39;d recommend reading the full article, but a few points are worth extracting:</p>

<ul>
<li>Mythos (and OpenAI&#39;s competitor, GPT5.5) <em>are</em> improvements over other models</li>
<li>However they are fairly well within existing trends of model improvement, and not outside anticipated performance</li>
<li>This result varies depending on which benchmark is used</li>
<li>They have downsides: they are very expensive to run for complex tasks</li>
</ul>

<p>Here&#39;s a trend line from the pointestimate article:</p>

<p><img src="/assets/img/epidemiology/graph.png" alt=""></p>

<p>This shows the new models (Mythos and GPT5.5) as well within the thresholds of existing trends.
Again bear in mind this is just showing performance on <em>one</em> benchmark, with relatively few datapoints. It&#39;s not immediately clear why a curve with that number of parameters was chosen, but you could probably fit a linear trend too.</p>

<p>Now here&#39;s another fucking monstrosity of a graph that I saw doing the rounds on social media: </p>

<p><img src="/assets/img/epidemiology/dumb.png" alt=""></p>

<p>The original source can be found here:<a href="https://waitbutwhy.com/2015/01/artificial-intelligence-revolution-1.html">https://waitbutwhy.com/2015/01/artificial-intelligence-revolution-1.html</a>, which to be fair, has a bit more of a nuanced explanation. Additionally it&#39;s from 2015 discussing potential future advances in AI</p>

<p>While this particular graph doesn&#39;t claim to show <em>actual</em> AI advances, and isn&#39;t based on any recent data, some people have been comparing this &quot;prediction&quot; with the now-famous Model Evaluation &amp; Threat Research group (METR) report: Measuring AI Ability to Complete Long Tasks<label for='4Yh72' class='margin-toggle sidenote-number'></label><input type='checkbox' id='4Yh72' class='margin-toggle'/><span class='sidenote'><a href="https://metr.org/blog/2025-03-19-measuring-ai-ability-to-complete-long-tasks/">https://metr.org/blog/2025-03-19-measuring-ai-ability-to-complete-long-tasks/</a> </span>, which has been widely shared as showing exponential trend in capability for new AI models.  There&#39;s a good 
analysis of this here MIT Technology Review: <a href="https://www.technologyreview.com/2026/02/05/1132254/this-is-the-most-misunderstood-graph-in-ai/">https://www.technologyreview.com/2026/02/05/1132254/this-is-the-most-misunderstood-graph-in-ai/</a>.</p>

<p><label for='ygCa3' class='margin-toggle'>&#8853;</label><input type='checkbox' id='ygCa3' class='margin-toggle'/><span class='marginnote'><img class='fullwidth' src='/assets/img/epidemiology/metr.png'/><br></span>
<label for='gVnPe' class='margin-toggle'> &#8853;</label><input type='checkbox' id='gVnPe' class='margin-toggle'/><span class='marginnote'>Just a warning, this graph has been doing the rounds both in linear form (shown here) AND logarithmic form. It&#39;s good of the authors to provide both, but it&#39;s a bit confusing that there are now 2 different versions of the graph being circulated </span>
When people share this graph, they&#39;re attempting to convey the following:</p>

<ul>
<li>Historic analysis suggests a linear improvement in model capability</li>
<li>New models (Mythos / GPT5.5) break that trend</li>
<li>The overall trend in model performance is exponential, and this is the start of that</li>
</ul>

<p>This is flawed analysis for a number of reasons:</p>

<p><strong>Lack of data</strong></p>

<p>First off, this is a pretty sparse dataset to use for any sort of prediction. The graphs from METR have ~ 25 datapoints belonging to state of the art (SOTA) frontier models, not really enough for confident long term predictions, although all we have to work with for the time being.</p>

<p><strong>Mis-representation of complex data</strong></p>

<p>This graph is representing the performance of LLMs as a scalar variable: a single number that goes up or down. Obviously this is over-simplistic. You can measure models in a wide variety of different benchmarks. Our situation is even worse, since plenty of the people publishing benchmarks are motivated to fudge the numbers, and there is evidence the models are just learning the training / valuation data, leading to having to adjust and shift benchmarks<label for='VXhSO' class='margin-toggle sidenote-number'></label><input type='checkbox' id='VXhSO' class='margin-toggle'/><span class='sidenote'>Discussed here: <a href="https://pointestimate.substack.com/p/how-good-is-mythos#footnote-3">https://pointestimate.substack.com/p/how-good-is-mythos#footnote-3</a> </span>. If you look on the Ollama model pages, every single model somehow comes up with a benchmark that shows themselves as the most performant model.</p>

<p><strong>Lack of prediction of exponential trends</strong></p>

<p>This is neatly shown in that chapter of &quot;The Signal and the Noise&quot;, when it comes to extrapolating exponential trends. It is very hard to predict exponential trends without a real understanding of the system, as you simply do not know the exponent. And since the data is exponential, small errors compound <em>massively</em>. This graph shows possible predictions for spread of AIDs, based on 5 years of data, with 95% prediction band. In other words at any point on the X axis, given the observed trend, 95% of possible futures are between the error bands.<label for='8jdg5' class='margin-toggle sidenote-number'></label><input type='checkbox' id='8jdg5' class='margin-toggle'/><span class='sidenote'>In fact, scientists had miscalculated the exponent, and the true rate of infection was significantly higher </span></p>

<div align="center">
<img src="/assets/img/epidemiology/error.jpg" width=600px >
</div>

<p>As you can see, there is an insane range of possible future outcomes and results based on this data. And this is when we <em>know</em> that the data obeys an exponential trend, as infectious diseases do, not when you are guessing the shape of the curve.</p>

<p>By contrast, the graph shown within the METR does not have 5 years of data and decades of study as communicable diseases have. </p>

<p>Now, we can&#39;t say that the increase in LLM capability <em>won&#39;t</em> be exponential, it might be. But you can&#39;t take a tiny number of studies, take 1 or 2 data points, plot arbitrary tangents and then confidently predict an exponential trend from that.</p>

<p>If you don&#39;t believe me, take a look at my awesome graph below, which magically can show almost any trend you want:</p>

<p><img src="/assets/img/epidemiology/trends.png" alt=""></p>

<p>Anyway, this is not to dismiss the work of the various safety institutes. There is serious work being done, and the 
research and graphics aren&#39;t to blame. The issue is problematic mis-interpretation of the data, or over-willingness to read trends from the data.</p>

<p><code>&lt;/rant&gt;</code></p>

<h2 id="summary">Summary</h2>

<p>Okay, rant on misleading graphs aside, This was an enjoyable experiment. Things wot I learned:</p>

<ul>
<li>Geometric vs binomial distributions, and survival functions</li>
<li>Difficulty inherent in modelling and predicting exponential trends </li>
<li>Factors affecting ability to predict infectious diseases, from Chapter 7, <em>The Signal and the Noise</em></li>
</ul>

<p>If you want another good book on the subject I can recommend <em>The Art of Uncertainty</em>, by David Speigelhalter. It too covers the ability to predict things, and how we reason about unknowns.</p>]]></content><author><name>Hendo</name></author><category term="games," /><category term="statistics" /><summary type="html"><![CDATA[Epidemiology is a serious subject and one I&#39;m not really an expert in. So while this is a fun experiment don&#39;t treat it as a serious public health thingy.]]></summary></entry><entry><title type="html">RADIO</title><link href="/radio/" rel="alternate" type="text/html" title="RADIO" /><published>2025-02-27T00:00:00-06:00</published><updated>2025-02-27T00:00:00-06:00</updated><id>/radio</id><content type="html" xml:base="/radio/"><![CDATA[<p>In Feburary last year I attended Disobey in Finland<label for='1vMdm' class='margin-toggle sidenote-number'></label><input type='checkbox' id='1vMdm' class='margin-toggle'/><span class='sidenote'><a href="https://disobey.fi/2025/">https://disobey.fi/2025/</a> </span>, it was a great event, although very cold. At the event was a good CTF, with a bunch of physical challenges that were hosted in the building. I didn&#39;t spend too much time in the CTF, but in this post I&#39;m going to write up my failed attempt to solve one of the challenges.</p>

<p>I didn&#39;t solve this at the event, just downloaded the capture file, and later took a look. As such, I didn&#39;t have the original challenge name, the description, or any other supporting files. All I had was this one file: <strong>capture.complex16</strong>. This was also my first time diving into practical signal analysis and radio hacking, so was a learning journey.</p>

<h2 id="radio-basics">Radio basics</h2>

<p>First off, it may be helpful to quickly familiarise yourself with the basics of radio signals:</p>

<p>Radio signals are transmitted via Sine waves in the RF spectrum. At a given point in time we can measure the FREQUENCY at which the wave oscillates, and the AMPLITUDE, or signal strength, of waves, using an antenna. </p>

<p><img src="/assets/img/radio/wave.png" alt="">
<p style="text-align:center;"><em>From <a href="https://nostarch.com/wireless-cookbook">The Wireless Cookbook</a>, figure 1-1</em></p></p>

<p>We communicate<label for='mf-id-2' class='margin-toggle'>&#8853;</label><input type='checkbox' id='mf-id-2' class='margin-toggle'/><span class='marginnote'><img class='fullwidth' src='/assets/img/radio/antenna.png'/><br></span> information by encoding the information into 1&#39;s and 0&#39;s, and then super-imposing this onto a sine wave. The wave without information is known as a carrier frequency, and the act of super-imposing of information onto a carrier is known as MODULATION. There are multiple forms of modulation:</p>

<ul>
<li>changing the Amplitude over time: this is known as Amplitude modulation (AM). AM of 1&#39;s and 0&#39;s is known as Amplitude Shift Keying (ASK).</li>
<li>changing the Frequency over time: this is known as Frequency modulation (AM). AM of 1&#39;s and 0&#39;s is known as Frequency Shift Keying (FSK).</li>
<li>changing the &quot;phase&quot; of the sine wave: this is known as Phase Shift Keying (PSK)</li>
</ul>

<p><img src="/assets/img/radio/modulation.png" alt="">
<p style="text-align:center;"><em>This diagram shows FSK as &quot;Digital Modulation&quot;</em></p></p>

<p>To retrieve information from a radio signal, we simply perform this process in reverse:</p>

<ul>
<li>we recieve the signal with an antenna, and do some horrible maths to recover the frequencies it contains</li>
<li>we identify and isolate a particular signal within the data (A frequency range containing a signal is referred to as a BAND or CHANNEL)</li>
<li>we DE-modulate the signal to recover 1&#39;s and 0&#39;s</li>
<li>we deconstruct the protocol and decode the data to recover the original information</li>
</ul>

<p>At some point we may need to perform some decryption as well, depending on the protocol.</p>

<p>This is what we are going to do for this challenge: we have a captured signal, and will deconstruct it to find the original information (a flag).</p>

<h2 id="loading-the-file">Loading the file</h2>

<p>The challenge gives us a file: <code>capture.complex16</code>. The file name tells us this is a captured radio signal using 16 bytes foir I/Q data (<a href="https://github.com/jopohl/urh/wiki/Supported-signal-file-formats">https://github.com/jopohl/urh/wiki/Supported-signal-file-formats</a>). Therefore we&#39;re going to use the excellent and intuitive tool URH<label for='vWPec' class='margin-toggle sidenote-number'></label><input type='checkbox' id='vWPec' class='margin-toggle'/><span class='sidenote'><a href="https://github.com/jopohl/urh?tab=readme-ov-file">https://github.com/jopohl/urh?tab=readme-ov-file</a> </span> to analyse the data, and hopefully extract a flag.</p>

<p>First off, we download and open URH: </p>
<div class="highlight"><pre><code class="language-sh" data-lang="sh">pipx <span class="nb">install </span>urh
</code></pre></div>
<p>We then load the capture.complex16 file, and see ... err nothing? Taking a closer look at the <a href="https://github.com/jopohl/urh/wiki/Supported-signal-file-formats">file formats supported</a>, it looks like URH relies on the file extension to guess the file type, since it imports raw binary data with no headers etc. </p>

<p>We copy the file to 2 files for the 16bit format: <code>complex.caputure16s</code> / <code>complex.caputure16u</code>, and open these:</p>

<ul>
<li><code>.caputure16u</code> shows a signal, but it doesn&#39;t look like a proper spectrum and URH complains it cannot understand it</li>
<li><code>.caputure16s</code> actually looks much more like what we expect, and URH instantly is able to understand it. So it must be this one.</li>
</ul>

<p>The first thing we see is the Spectrogram view. This shows a view of the strength of each frequency over time, as a visualisation of the RF spectrum. We can see distinct &quot;bursts&quot; of traffic, we will call these &quot;packets&quot;, which we will hypothesize contain a number of &quot;symbols&quot; (1&#39;s / 0&#39;s), and we can see our data is contained in a single band. </p>

<p><img src="/assets/img/radio/signal_1.png" alt=""></p>

<p>URH also tells us the length of the conversation is 14 seconds, although this isn&#39;t hugely helpful here. If we look at the number of symbols within the timespan (you can just make them out as dark and light bars within each packet shown above) it tells us the thing encoding / decoding this signal is operating faster than a human could feasibly process: we are not dealing with human created morse code, typed out by hand, but with a digital system meant to be sent and processed by a computer.</p>

<p><img src="/assets/img/radio/morse_1.jpg" alt=""></p>

<p>First off we highlight just the frequency range we&#39;re interested in in URH, and select &quot;Apply bandpass filter&quot; from the right click menu. This crops the data to only these frequencies, like cropping the images of an image. Removing the redundant background noise helps later analysis and makes the image clearer. We also adjust the Data_min and Data_max values in URH to make the visualisation clearer.</p>

<!-- 
Decoding

sox -e float -t raw -r 192000 -b 32 -c 2 [path to downloaded IQ file.bin] -t wav -e float -b 32 -c 2 -r 192000 [path to output file.wav]
 -->

<h2 id="demodulation">Demodulation</h2>

<p>So the first step is to Demodulate the signal, ie recover 1&#39;s and 0&#39;s from the sine wave. URH in fact is pretty good at this for basic modulations such as ASK and FSK, and can do it automatically, but we&#39;ll just verify it manually.</p>

<p>First off we zoom in on a packet in the spectrogram view:
<img src="/assets/img/radio/spectrogram_1.png" alt=""></p>

<p>We can see here that the signal is switching between 2 distinct frequencies:</p>

<p><img src="/assets/img/radio/spectrogram_2.png" alt=""></p>

<p>If we switch to the Analogue view in URH, we can see amplitude vs time, which confirms that the frequency is changing over time within the signal. The darker sections have more wavelengths per unit of time, and are therefore higher frequency, while the lighter sections are lower:</p>

<p><img src="/assets/img/radio/fm_1.png" alt=""></p>

<p>Since we can observe the frequency switching between 2 distinct values (and not a continuous range of values) we will deduce this is a form of digital information (ie 1&#39;s and 0&#39;s) and that the modulation is <a href="https://www.allaboutcircuits.com/textbook/radio-frequency-analysis-design/radio-frequency-modulation/frequency-modulation-theory-time-domain-frequency-domain/">Frequency Shift Keying (FSK)</a>. In URH we can now switch the modulation type to FSK, and move to the &quot;Demodulated view&quot; which shows us how to interpret the data. We drag the centre line between the clear ridges, and this is how we determine each &quot;bit&quot; of data. If the frequency is above the centre it is a 1, and if it is below it is a 0. We can invert these later if we want, when doing further decoding, but for the time being we only care about separating the 1&#39;s from the 0&#39;s. We can tell this is the correct decoding, as there is a clear separation between the peaks and troughs:</p>

<p><img src="/assets/img/radio/fm_2.png" alt=""></p>

<p>We can see that the various peaks and troughs differ in length. We usually interpret this as being repeated symbols, ie <code>11</code> would be represented as a peak twice as wide as a <code>1</code> by itself. To fully demodulate the data we need to work out the length of each individual symbol. Luckily URH does this for us, and guesses 1200, which turns out to be right. But we can also do it ourselves. Typically we look for the greatest common denominator of the various lengths represented, and smallest individual peak / trough. Here, if we select a small bump, URH tells us that it is ~ 1200 samples long. If we select a few other peaks / torughs at random we can see they are all multiples of 1200 (2400, 4800). This gives a good level of confidence that the width of each symbol is 1200 samples long.</p>

<p><img src="/assets/img/radio/peak_1.png" alt=""></p>

<p>If we take a closer look at both this demodulated view, and the original spectrogram, we can see some interesting properties of the underlying radio signal:</p>

<p><img src="/assets/img/radio/ringing_1.png" alt=""></p>

<p>At each &quot;Square&quot; wave, we see a spike, that calms down to a more stable frequency, these are &quot;overshoot&quot; and &quot;ringing&quot; signals, <a href="https://en.wikipedia.org/wiki/Ringing_(signal)">https://en.wikipedia.org/wiki/Ringing_(signal)</a>, as described in CRYPTONOMICON&#39;s illustration of Van Eck phreaking:</p>

<p><img src="/assets/img/radio/van_eck.jpg" alt=""></p>

<p>We can also see radiating &quot;echoes&quot; of the original frequencies, diminishing in strength the further from the carrier frequency. These are SIDELOBES, generated as an artifact of antenna mechanics (<a href="https://en.wikipedia.org/wiki/Sidelobes):">https://en.wikipedia.org/wiki/Sidelobes):</a></p>

<p><img src="/assets/img/radio/sidelobes_1.png" alt=""></p>

<p>These give us a strong indication this signal was generated from an actual capture, and not synthetically generated</p>

<h2 id="decoding">Decoding</h2>

<p>Now we think we&#39;ve worked out how to convert the wave to 1&#39;s and 0&#39;s by demodulating it, let&#39;s take a look at retrieving actual information from the bits, ie decoding. Given our demodulation parameters: FSK with a centre frequency of 0.015 and a samples/symbol of 1200, we get the following bits for each message:</p>
<div class="highlight"><pre><code class="language-" data-lang="">011110000111100001000001011111100100010100111001100000000000000011110010000
111001010010101110011101001001010001001111111010011100011001100000101000000
[Pause: 2080068 samples]

011110000111100001000001011111100100010100111001100000001000000000000000001
1010110000
[Pause: 2174415 samples]

011110000111100001000001011111100100010100111001100000010000000101010110001
01000001101011010
[Pause: 2049324 samples]

011110000111100001000001011111100100010100111001100000011000000000000001010
11001100
[Pause: 2115700 samples]

000001111000011110000100000101111110010001010011100110000010000000010101000
000111000000011001101010000000101100000001101001111110011100100010110101000
111011101111101101110000111111000110110110010101101001001010011101010010000
100001111101101110000000001001110111101001111010100100111001101101000110110
001110010110101100111100000000111110011111100010100111001100001010101100000
1110011000100101111101011101111100011011100001110100100000000
[Pause: 3539490 samples]
</code></pre></div>
<p>Here again in hexadecimal:</p>
<div class="highlight"><pre><code class="language-" data-lang="">7878417e45398000f21ca573a4a27f4e330500
[Pause: 2080029 samples]

7878417e45398080003580
[Pause: 2174377 samples]

7878417e45398101562835a
[Pause: 2049300 samples]

7878417e4539818001598
[Pause: 2115661 samples]

07878417e4539820150380cd40580d3f3916a3bbedc3f1b656929d4843edc013bd3d49cda3639
6b3c03e7e29cc2ac1cc4bebbe370e90000
[Pause: 3539490 samples]
</code></pre></div>
<p>And here in ASCII (printable chars only):</p>
<div class="highlight"><pre><code class="language-" data-lang="">xxA~E9���s��N3%

xxA~E9��5�%

xxA~E9�V(5� (LSB padded with 0)

?9�����V��HC���=Iͣc���&gt;~)�*��K�7�%
</code></pre></div>
<p>So far, so meaningless. If we were hoping this would nicely translate to human readable text we were wrong. First off, let&#39;s dump this into Cyberchef, using the binary representation and us a recipe <code>From Binary &gt; Brute force text encoding (decode)</code><label for='QONK6' class='margin-toggle sidenote-number'></label><input type='checkbox' id='QONK6' class='margin-toggle'/><span class='sidenote'>Cyberchef recipe link: <a href="https://gchq.github.io/CyberChef/#recipe=From_Binary(Space,8)Text_Encoding_Brute_Force(Encode)&input=MDExMTEwMDAwMTExMTAwMDAxMDAwMDAxMDExMTExMTAwMTAwMDEwMTAwMTExMDAxMTAwMDAwMDAwMDAwMDAwMDExMTEwMDEwMDAwMTExMDAxMDEwMDEwMTAxMTEwMDExMTAxMDAxMDAxMDEwMDAxMDAxMTExMTExMDEwMDExMTAwMDExMDAxMTAwMDAwMTAxMDAwMDAw&oeol=NEL">https://gchq.github.io/CyberChef/#recipe=From_Binary(Space,8)Text_Encoding_Brute_Force(Encode)&amp;input=MDExMTEwMDAwMTExMTAwMDAxMDAwMDAxMDExMTExMTAwMTAwMDEwMTAwMTExMDAxMTAwMDAwMDAwMDAwMDAwMDExMTEwMDEwMDAwMTExMDAxMDEwMDEwMTAxMTEwMDExMTAxMDAxMDAxMDEwMDAxMDAxMTExMTExMDEwMDExMTAwMDExMDAxMTAwMDAwMTAxMDAwMDAw&amp;oeol=NEL</a> </span>. This will show us a table of the messages decoded using a variety of text encodings, which helps identify any unusual encodings. We also run this with a bit length of 7 and 8, to account for different character lengths. We can also try inserting a &quot;swap endianness&quot; block, to check both LSB and MSB, as radios / networks often work with either. Nothing massively helpful comes out of this, if we were hoping a flag would jump out at us, all the potential decodings include seemingly random non-printable characters.</p>

<p><img src="/assets/img/radio/decode_1.png" alt=""></p>

<p>The only clear pattern we can make out is that the first 4 messages begin with the binary/ hexadecimal string:</p>
<div class="highlight"><pre><code class="language-" data-lang="">Binary: 0111100001111000010000010111111001000101001110011000000
Hexadecimal: 7878417e45398

</code></pre></div>
<p>Which decodes in ASCII to <code>xxA~E9</code>, which has no obvious meaning.</p>

<p>This is 55 bits long, an odd length, not a multiple of 7 or 8 commonly associated with ASCII / byte encodings. it is divisible by 5, which is the symbol length of the original Baudot encoding, used in telegraphs and punched tape readers <label for='5ooFG' class='margin-toggle sidenote-number'></label><input type='checkbox' id='5ooFG' class='margin-toggle'/><span class='sidenote'><a href="https://en.wikipedia.org/wiki/Baudot_code">https://en.wikipedia.org/wiki/Baudot_code</a> </span>, putting the messages into <a href="https://www.dcode.fr/baudot-code">https://www.dcode.fr/baudot-code</a> yeilds no obvious plaintext either:</p>

<p><img src="/assets/img/radio/baudot_1.png" alt=""></p>

<p>A link in Wikipedia mentions the Bacon cipher, which uses groups of 5 binary symbols. A quick experiment in cyberchef also shows nothing from this.</p>

<p>At this point we google the string <code>xxA~E9</code>, in case it&#39;s a common identifier for some known radio protocol. This reveals nothing however. Another potential approach is to shave bits off the beginning of the message, efectively bitshifting the result. This is in case the protocol beings with a number of bits before beginning the 7 / 8 bit encoding scheme, which would misalign cyberchef&#39;s results. However looking at each of the various offsets between 1 and 8 reveals nothing.</p>

<p>If we look at the final message we see it doesn&#39;t begin with this sequence, but that the sequence is offset into the start of the message, except for the 55th bit, which is different:</p>

<p><img src="/assets/img/radio/motif_1.png" alt=""></p>

<p>This could mean the motif serves as some kind of identifier to signify the start of data, in case of glitches. This is known as a <a href="https://en.wikipedia.org/wiki/Syncword">PREAMBLE</a>.</p>

<p>If we look at the binary representation, a couple of things can be made out:</p>

<p>while offset by the first bit, we see oscillating patterns of 4:
 - 4 1&#39;s
 - 4 0&#39;s
 - 4 1&#39;s
 - 4 0&#39;s</p>

<p>if we take these as 8 bit symbols we have some nice and symmetrical:</p>
<div class="highlight"><pre><code class="language-" data-lang="">without starting 0:
11110000 (240 decimal)
11110000 (240 decimal)

with starting 0:
01111000  (120 decimal, x ASCII)
01111000  (120 decimal, x ASCII)

</code></pre></div>
<p>This also means that taken from the first symbol and divided into groups of 4 or 8, the chunks are inverses:</p>
<div class="highlight"><pre><code class="language-" data-lang="">0111
1000
0111
1000

</code></pre></div>
<p>However both these patterns only hold for the first 16 / 17 symbols, and don&#39;t explain the rest of the pattern. They do kind of <em>feel</em> like a preamble, since preambles often use alternating patterns of a fixed length to syncronise the bitrate, but they convey no clear meaning. They also don&#39;t really help understand the rest of the message.</p>

<p>URH actually has an entire dedicated tab for analysis, so lets use this:</p>

<p><img src="/assets/img/radio/analysis_1.png" alt=""></p>

<p>This allows us to select various decoding techniques, visualise the output, and analyse the structure of each message. Here we have selected the default <a href="https://en.wikipedia.org/wiki/Non-return-to-zero">Non Return to Zero (NRZ)</a> encoding, which is just an irritating way of saying high=1, low=0, as we&#39;d already assumed. On the right we can see that URH has auto-highlighted sections of the message and highlighted them. These highlights are the tools way of guessing what the different segments mean, and here it&#39;s guessed the same as we did: the xxA~E9 is some kind of preamble / sync word. It thinks the green blobs are checksums (checksums are usually at the end of the message), which might be the case, but we&#39;d need to find an algorithm that makes sense. So this decoding doesn&#39;t yield results, but what about the others. I tried cycling through all the inbuilt encodings URH knew about:</p>

<ul>
<li>morse code</li>
<li>inverse NRZ</li>
<li>manchester encoding</li>
<li>differential manchester encoding</li>
</ul>

<p>But no luck. URH also allows you to create your own decodings, both using the inbuilt drag and drop architecture, or via an external program. An external program would help if I knew what the encoding scheme as, but I don&#39;t. Instead i cobble together a few basic potential encodings with the editor:</p>

<p><img src="/assets/img/radio/analysis_2.png" alt=""></p>

<p><br></p>

<ul>
<li>Invert bits</li>
<li>Invert endianness</li>
<li>Remove <a href="https://www.allaboutcircuits.com/technical-articles/whitening-filters-help-low-power-radios-tackle-issues-caused-by-long-identical-bit-sequences/">data whitening</a></li>
<li>Differential encoding</li>
</ul>

<p>For each of these, I take the binary output and run through the earlier cyberchef recipe to detect potential text encodings for both 7 / 8 bit digits.</p>

<p>Still nothing.</p>

<p><strong>Conclusions:</strong></p>

<ul>
<li>the message is not a straightforward text encoding</li>
</ul>

<h3 id="symbol-analysis">Symbol analysis</h3>

<p>Let&#39;s see if the contents of the symbols tell us something about the mesage. The characters used and their frequency can often give an interesting insight into the contents and structure of a binary message. To do this we&#39;ll generate a quick frequency histogram of characters taken from the binary decoding using Cyberchef:</p>

<p><img src="/assets/img/radio/entropy_1.png" alt=""></p>

<p>If this had resulted in a small spike of, say 26 symbols (the length of the alphabet), it would imply we were dealing with a nice and simple substitution cipher, ROT13 etc. We can also intuit those types of simple ciphers by looking for common patterns in the flag format that are echoed in the ciphertext:</p>

<p><img src="/assets/img/radio/cryptanalysis_1.png" alt=""></p>

<p>However the data looks pretty much random and evenly distributed between 0 and 256, which potentially implies one of several conclusions:</p>

<ul>
<li>The data is encrypted, as encryption algorthims are often engineered to produce <a href="https://en.wikipedia.org/wiki/Entropic_security">data indistinuishable from random streams</a></li>
<li>The data is encoded / compressed: compression algorithms will seek to make full use of the available symbol alphabet, obscuring frequency patterns in the original data</li>
</ul>

<p>However with this short a signal, we are unlikely to be able to draw many conclusions from this: <a href="https://en.wikipedia.org/wiki/Frequency_analysis">frequency analysis</a> is only effective on sufficiently long texts for patterns to emerge, and relative frequencies to be realised. Compression seems redundant for messages this short, but as this is a CTF we&#39;ll briefly explore the hypothesis that the data is encrypted.</p>

<p>We&#39;ve already established that we don&#39;t think it&#39;s an encryption form based on substitution cipher, due to the entropy of the data, and lack of repeated patterns in the ASCII. One common form of simple encryption, used in insecure protocols and commonly seen in CTFs is <a href="https://en.wikipedia.org/wiki/XOR_cipher">XOR</a>. XOR encryption would result in output characters across the 8-bit range (0-256) as we appear to be seeing here.</p>

<p>If the encryption used was XOR: the repeated pattern of xxA~E9 could be an artifact of XOR encryption using a key against a pre-amble consisting of 0&#39;s, a form of &quot;known plaintext&quot; attack against XOR. Trying this in cyberchef reveals no meaningful data however. A couple of other quick and dirty checks for XOR encryption:</p>

<ul>
<li>XOR brute force (key len 1): nothing</li>
<li>XOR brute force (key len &gt;1 with crib: disobey: nothing</li>
</ul>

<hr>

<p>At this point, we are likely over-complicating things, and should fall back on occams razor: &quot;other things being equal, simpler explanations are generally better than more complex ones&quot;. We&#39;ve begun to make too many assumptions, assuming the message is encrypted, assuming the form of the encryption (e.g XOR), and then beginning to assume the key length and the plaintext format. </p>

<p>This is the NSA&#39;s first law of cryptanalysis: look for plaintext.</p>

<p>In fact the simplest explanation for the <code>xxA~E9</code> preamble is that it is simply a preamble, and not any form of leaked XOR key, and we should fall back to this line of thought before beginning to engage in fanciful explorations of cryptography.</p>

<p><strong>Conclusions:</strong></p>

<ul>
<li>Until we find evidence, the signal is unlikely to be encrypted or compressed</li>
</ul>

<!-- ### FREQUENCY

When decoding a radio signal, one hint as to how to interpret the data comes in the form of the frequency: particular applications and protocols will be associated with particular portions of the RF spectrum. Looking up known radio protocols that use the frequency of our signal may tell us how to decode the signal.

https://institutionofelectronics.ac.uk/2025/01/14/part-1-a-beginners-guide-to-the-power-of-iq-data-and-beauty-of-negative-frequencies/

To find the frequency we can use URH, which tells us:

LOW = -0.1476
HIGH = 0.2108

centre = 0.0422
?????????????????????

This gives a bandwidth of 

Conclusions:

frequency:
http://www.radioreference.com/
 -->

<h3 id="ask">ASK??</h3>

<p>Near the beginning of the challenge, we noticed the signal appeared to be modulated in the frequency domain (ie, using FSK). But was that an assumption we should have proceeded with? Let&#39;s quickly double check whether the signal could in fact be modulated in the Amplitude domain, ie, using ASK.</p>

<p>If we examine the analog signal, we can in fact see slight differences in amplitude; these could be artefacts of <a href="https://en.wikipedia.org/wiki/Amplitude-shift_keying">ASK modulation</a></p>

<p><img src="/assets/img/radio/ask_1.png" alt=""></p>

<p>However, if we actually look closer at the signal we can see 2 reasons this doesn&#39;t quite make sense:</p>

<ul>
<li>The differences in amplitude are too small to be discernable amongst the noise: we would expect much more distinct peaks and troughs</li>
<li>The differences in amplitude actually align with the changes in frequency, and don&#39;t appear to convey any extra information</li>
</ul>

<p><img src="/assets/img/radio/ask_2.png" alt=""></p>

<p><strong>Conclusion:</strong></p>

<ul>
<li>the signal is not ASK</li>
</ul>

<h3 id="signal-lengths">Signal lengths</h3>

<p>Another point about the signals we haven&#39;t examined is the length of each message: for ease of processing radio signals will often be sent in &quot;packets&quot; of predictable lengths, to help the receiver distinguish individual messages in a potentially noisy channel. If we look at the bit lengths of our messages we see the following:</p>

<table><thead>
<tr>
<th>Message 1</th>
<th>150 bits</th>
</tr>
</thead><tbody>
<tr>
<td>Message 2</td>
<td>85 bits</td>
</tr>
<tr>
<td>Message 3</td>
<td>92 bits</td>
</tr>
<tr>
<td>Message 4</td>
<td>83 bits</td>
</tr>
<tr>
<td>Message 5</td>
<td>436 bits</td>
</tr>
</tbody></table>

<p>A couple of observations here:</p>

<ul>
<li><p>The messages are not all the same length, as a very rough estimate we have:</p>

<ul>
<li>1 medium (msg1 = 150)</li>
<li>3 short (msg2, msg3, msg4 ~= 86)</li>
<li>1 long (msg5 = 436)</li>
</ul></li>
<li><p>The fifth and final message is significantly longer than the others. One interpretation of this is that packets 1-4 are some handshake, and the fifth contains the data payload:</p>

<ul>
<li>short messages are usually used to convey signalling data (think like TCP SYN/ACK/SYNACK meta packets)</li>
<li>long messages are often associated with the actual transmission of data</li>
</ul></li>
</ul>

<p>Therefore the fifth packet is most likely where our data (flag) is, as it is longer than the others, and long enough to contain a flag, although this is an assumption.</p>

<ul>
<li>There is no common denominator between all lengths (83 is a prime). This has multiple explanations:

<ul>
<li>minor glitches in the signal have resulted in incorrect demodulation, throwing off some messages length by 1 or 2</li>
<li>packets do not represent compositions of even-length symbols. For example, if all our packets could be decoded to ASCII, we would expect them to all be multiples of 7 / 8. If messages were AES encrypted, we could expect them to roughly align with the blocksize of AES, and if Baudot code, 5 bits.</li>
</ul></li>
</ul>

<h2 id="participants">Participants</h2>

<p>Radio communications can either be unidirectional (ie broadcast), whereby a single party distributes information to a large number of potential receivers (e.g television), or a bi-directional communication between 2 or more parties. In other words, a given signal can represent information from A-&gt; B, or from both A-&gt;B, and B-&gt; A. In fact radio signals can even represent communications between much larger numbers of participants (<a href="https://en.wikipedia.org/wiki/Channel_access_method">https://en.wikipedia.org/wiki/Channel_access_method</a>).</p>

<p>It might be relevant to the challenge to understand if we&#39;re looking at the messages between multiple parties, or listening to a solitary ALICE screaming into the void. For example if we&#39;re looking at 2 parties, we might want to see if we can distinguish some form of key-exchange happening that results in later messages being encrypted.</p>

<p>Let&#39;s attempt to work out if the traffic we&#39;re examining belongs to one or more parties*.</p>

<ul>
<li>of course, we could simply be observing one half of a bi-drectional converstation, with our capture cropped to only one side of the conversation</li>
</ul>

<h3 id="multi-party-comms">Multi-party comms</h3>

<p>First off, we are going to discard the idea that our signal could be a conversation between multiple parties, for the following reasons:</p>

<ul>
<li>the number of packets (5) is too few</li>
<li>the length of packets is too short</li>
<li>the aforementioned &quot;Occam&#39;s razor&quot;: we shouldn&#39;t assume too much complexity, trying to discern a mesh network for a simple CTF challenge is getting a little silly</li>
</ul>

<p><strong>Why the number of packets is relevant:</strong>
We only have 5 packets: a meaningful conversation between &gt;2 parties is likely going to be significantly more involved than this, as the total number of packets would be divided somehow between the number of parties present. On the other hand 5 signals could reasonably represent a short but meaningful exchange between 2 parties:</p>

<ul>
<li>Hi I&#39;m ALICE</li>
<li>Hi ALICE, I&#39;m BOB</li>
<li>Nice to meet you BOB, do you want a FLAG?</li>
<li>Yes please ALICE, send that over</li>
<li>Sure thing BOB: here&#39;s the FLAG: FLAG{askjdhakdhhasd}</li>
</ul>

<p><strong>Why The length of messages is relevant:</strong></p>

<p>When you recieve a radio message for a multi-party communication, you have to work out who it is to / from. For a bi-party channel this could be simply represented with a single bit: 0 to represent ALICE and 1 to represent BOB. However when you have more participants, and especially when you have a communication medium with an unknown and potentially high number of parties, the process of establishing who is who, and who the message is meant for, begins to take substantially more bits.</p>

<p>As an example of this let&#39;s take Wi-Fi: <a href="https://community.cisco.com/t5/wireless-mobility-knowledge-base/802-11-frames-a-starter-guide-to-learn-wireless-sniffer-traces/ta-p/3110019">https://community.cisco.com/t5/wireless-mobility-knowledge-base/802-11-frames-a-starter-guide-to-learn-wireless-sniffer-traces/ta-p/3110019</a>.</p>

<p>Wi-Fi networks are designed to support large numbers of devices all communicating on the same frequency. As a result each Wi-Fi packet needs to include headers / footers with the following properties:</p>

<ul>
<li>who the sender is </li>
<li>who the destination is</li>
<li>various sequence numbers / checksums to avoid confusion when messages interfere / are descynchronised</li>
</ul>

<p>As you can see in a Wi-Fi packet header this takes up a significant amount of data, purely for the header. In fact a Wi-Fi header by itself is longer than our shortest packet (~36 bytes vs 83 bits)</p>

<p><img src="/assets/img/radio/wifi_packet.png" alt=""></p>

<p>Additionally, to avoid interference, the parties need to observe a strict timing pattern to avoid confliction<label for='RJLYY' class='margin-toggle sidenote-number'></label><input type='checkbox' id='RJLYY' class='margin-toggle'/><span class='sidenote'><a href="https://en.wikipedia.org/wiki/Duplex_(telecommunications)#Time-division_duplexing">https://en.wikipedia.org/wiki/Duplex_(telecommunications)#Time-division_duplexing</a> </span>. This involves distributing data in short sharp bursts, with gaps between. The parties need to synchronise their communications and also issue a bunch of packets to indicate who can send data (CTS = Clear To Send: <a href="https://en.wikipedia.org/wiki/IEEE_802.11_RTS/CTS">https://en.wikipedia.org/wiki/IEEE_802.11_RTS/CTS</a>). As a result here&#39;s what a radio spectrogram of a Wi-Fi network looks like: it&#39;s much noisier and choppier than our comms, as any available time becomes filled with synchronisation signals.</p>

<p><img src="/assets/img/radio/wifi_spectrum.png" alt=""></p>

<p>Now these are all assumptions we can argue with: there are simpler multi-party protocols than Wi-Fi that don&#39;t require as much overhead. But the point remains that if we were looking at a capture of a communications protocol between N (N&gt;2) parties, we would expect it to be noisier, and the messages longer. So we will proceed with the assumption that our capture is of either 1 or 2 parties.</p>

<p><strong>Conclusions:</strong></p>

<ul>
<li>our capture does not represent communications between more than 2 participants</li>
</ul>

<h4 id="figuring-out-how-many-participants-signal-strength">figuring out how many participants: signal strength</h4>

<p>According to laws of physics, signal strength of radio communications decreases with distance exponentially<label for='KdnU0' class='margin-toggle sidenote-number'></label><input type='checkbox' id='KdnU0' class='margin-toggle'/><span class='sidenote'><a href="https://en.wikipedia.org/wiki/Attenuation">https://en.wikipedia.org/wiki/Attenuation</a> </span>. This means that if we record a radio signal that represents a multi-party communication, messages belonging to different parties may have different average strengths (amplitudes).</p>

<p>Think about if you are stood between 2 people yelling at each other from a long distance away: if you are stood next to one, and far from the other you will hear one very loud voice, and one quieter. Therefore, even if you could not recognise the distinct voices of the participants, you could make a good guess that you are hearing a conversation between 2 parties, simply from the fact you can make out 2 distinct volumes.</p>

<p>We can apply this principle to radio analysis in the same way. A captured radio signal has to be captured from <em>somewhere</em><label for='E59a8' class='margin-toggle sidenote-number'></label><input type='checkbox' id='E59a8' class='margin-toggle'/><span class='sidenote'>err actually we could be looking at a purely synthetic / simulated signal generated in software, or drawn on graph paper. But if we look at the ringing, sidelobes etc we showed earlier, it seems likely this is a real signal capture </span>
. So depending on where the signal was captured, our interception point will have a distance between each side of the conversation, shown in the below image:</p>

<p><img src="/assets/img/radio/distance_1.png" alt=""></p>

<p>Unless we are at point Z: equidistant from either party, then one party will be stronger, and the other weaker. If we are at X, we will observe A as the stronger signal, and vice versa if we are point Y.</p>

<p>Now in fact, we don&#39;t just consider points on a straight line, antennas broadcast signals in multiple directions, depending on antenna construction. We can think of a distance from a signal being represented by a radius around the transmitter, and the relative signal strengths represented in 2 dimensions, in a kind of venn diagram. This can be taken further with 3 signals, used for triangulation, as in GPS or radio direction finding (GPS / DF / CRYPTO: <a href="https://www.cryptomuseum.com/df/df.htm">https://www.cryptomuseum.com/df/df.htm</a>)</p>

<p>Anyway, let&#39;s put this principle in practice. If we are looking at comms between 2 parties, we may be able to determine who each is, by looking at the amplitude of each packet. An example capture that shows distinct amplitudes belonging to multiple participants is shown below:</p>

<p><img src="/assets/img/radio/rssi_1.png" alt=""></p>

<p>Do we see this when looking at our capture??</p>

<p><img src="/assets/img/radio/rssi_2.png" alt=""></p>

<ul>
<li>not really: there&#39;s no massively discernible difference in strenght between packets</li>
<li>because of the exponential drop off in signal strength, we&#39;d expect even a relatively small difference in distance to result in discernably different amplitudes</li>
<li>If we actviate URH&#39;s auto &quot;participant detection&quot;, it also doesn&#39;t distinguish relative RSSI&#39;s of different packets</li>
</ul>

<p>Conclusions:
Either
    - our capture source is perfectly equidistant between both parties
    - our capture source is close enough to both parties there is no discernible stength difference
    - we are only seeing communications from a single party
    - the captured signal has been artificially edited to make both parties equally strong (sometimes used to make decoding easier in a noisy channel for modulations that are not ASK)</p>

<p>Either way, it seems highly unlikely that the party a message comes from conveys any meaningful information needed to obtain the flag, as it cannot be easily discerned. We will assume it is not important.</p>

<p><strong>Conclusions:</strong></p>

<ul>
<li>the sender of each packet is not relevant to it&#39;s decoding</li>
</ul>

<hr>

<p>There&#39;s yet another potential complication: multi path signals. In the real world there are multiple surfaces off which radio signals are reflected before hitting the receiver.In a building, signals bounce off walls (explaining odd patches of wifi strength / weakness in houses), and outside, tunnels, mountains, clouds, and layers of the ionosphere all reflect different signals. The degree of reflection is proportionate to the frequency / characteristics of the signal, but the resulting effect is that if you take a single signal and broadcast it, the waves will be scattered by the different objects they encounter. This creates multiple paths from source to destination, and the reciever will likely recieve the signal multiple times at different strengths and at different times. This is the same principle of shouting &quot;ECHO&quot; in cave or tunnel: you hear the sound reflected back at different intervals, representing the multiple lengths that the sound wave took to be reflected back to you<label for='bg7sc' class='margin-toggle sidenote-number'></label><input type='checkbox' id='bg7sc' class='margin-toggle'/><span class='sidenote'><a href="https://en.wikipedia.org/wiki/Multipath_propagation">https://en.wikipedia.org/wiki/Multipath_propagation</a> </span>.</p>

<div align="center">
<img src="/assets/img/radio/multipath.png" width=500px>
</div>

<p style="text-align:center;"><em>multipath reflections scatter a single signal signal across multiple routes</em></p>

<p>This reflection can even be harnessed to exchange information round corners or over the horizon, frequently used in sattelite signals / long distance communications<label for='OVCvt' class='margin-toggle'>&#8853;</label><input type='checkbox' id='OVCvt' class='margin-toggle'/><span class='marginnote'><img class='fullwidth' src='/assets/img/radio/ion.jpg'/><br></span>, and more recently used to generate 3D maps of structures from received radio signals<label for='r64q8' class='margin-toggle sidenote-number'></label><input type='checkbox' id='r64q8' class='margin-toggle'/><span class='sidenote'><a href="https://ieeexplore.ieee.org/document/10025551">https://ieeexplore.ieee.org/document/10025551</a> </span> (not the same as systems such as SONAR, RADAR, DOPPLER RADAR, or LIDAR, which typically subtract multi-path echoes to discard noise).</p>

<p>Our signal doesn&#39;t appear to be complicated by this effect, so we can safely proceed and ignore it&#39;s implications.</p>

<h3 id="file-type-analysis">File type analysis</h3>

<p>CTFs like to combine different disciplines in interesting ways, so while radio&#39;s are pretty analogue maybe theres some other digital funkiness going on. A common category of CTF challenge is forensics / stegnaography: looking for hidden meaning encoded in digital files, and that&#39;s effectively what we&#39;re doing here. We have a seamingly meaningless stream of binary data, and are aiming to recover a meaningful flag from it.</p>

<p>Let&#39;s try that with binwalk, and the <code>file</code> utilitiy, to pick out any file signatures in the data. We&#39;ll do this with the first and last messages, as they have the greatest lengths.</p>
<div class="highlight"><pre><code class="language-sh" data-lang="sh"><span class="nv">$ </span><span class="nb">echo</span> <span class="s2">"7878417e45398000f21ca573a4a27f4e330440"</span> | unhex <span class="o">&gt;</span> blob5.bin

<span class="nv">$ </span>file blob1.bin       
blob1.bin: data

<span class="nv">$ </span>binwalk <span class="nt">--dd</span><span class="o">=</span><span class="s2">".*"</span> blob1.bin 

DECIMAL       HEXADECIMAL     DESCRIPTION
<span class="nt">--------------------------------------------------------------------------------</span>


<span class="nv">$ </span><span class="nb">echo</span> <span class="s2">"07878417e4539820150380cd40580d3f3916a3bbedc3f1b656929d4843edc013bd3d49cda36396b3c03e7e29cc2ac1cc4bebbe370e9000"</span> | unhex <span class="o">&gt;</span> blob5.bin

<span class="nv">$ </span>file blob5.bin       
blob5.bin: data

<span class="nv">$ </span>binwalk <span class="nt">--dd</span><span class="o">=</span><span class="s2">".*"</span> blob5.bin 

DECIMAL       HEXADECIMAL     DESCRIPTION
<span class="nt">--------------------------------------------------------------------------------</span>

<span class="err">$</span>

</code></pre></div>
<p>Neither tool finds anything.</p>

<p><strong>Conclusion</strong>:</p>

<ul>
<li>the message does not represent an encoded file with a known filetype</li>
</ul>

<h2 id="eventual-analysis">Eventual analysis</h2>

<p>I wasn&#39;t able to decode the flag or extract any meaning from the messages. If we put together our conclusions we have the following hints:</p>

<ul>
<li>the capture is a digital transmission, sent and received by a computer not a human</li>
<li>the capture represents a real signal captured with radio hardware</li>
<li>the message is not a straightforward text encoding</li>
<li>the signal is not ASK</li>
<li>the signal is likely to be FSK encoded, with a sample/symbol of 1200</li>
<li>Until we find evidence, the signal is unlikely to be encrypted or compressed</li>
<li>the sender of each packet is not relevant to it&#39;s decoding</li>
<li>our capture does not represent communications between more than 2 participants</li>
<li>The fifth packet is most likely where our data (flag) is</li>
<li>the message does not represent an encoded file with a known filetype
Either:</li>
<li>minor glitches in the signal have resulted in incorrect demodulation, throwing off some messages length by 1 or 2</li>
<li>packets do not represent compositions of even-length symbols.</li>
</ul>

<p>So, in keeping with th spirit of the blog, I&#39;ve failed to slve this challenge, but in the process learned a lot about radio signals and decoding, found ut about the aweome URH tool, so it&#39;s still been fun. </p>

<blockquote>
<p>To be continued...</p>
</blockquote>

<h2 id="glossary">Glossary</h2>

<ul>
<li><p><a href="https://en.wikipedia.org/wiki/Syncword">PREAMBLE</a> A series of symbols to indicate the start of a message / packet</p></li>
<li><p>Frequency: The rate at which a radio wave oscillates, typically measured in Hertz (Hz). It determines the wave&#39;s position on the electromagnetic spectrum.</p></li>
<li><p>Amplitude: The strength of a radio wave, representing the power or intensity of the signal.</p></li>
<li><p>Modulation: The process of varying a property of a carrier wave (e.g., amplitude, frequency, or phase) to encode information for transmission.</p>

<ul>
<li>ASK (Amplitude Shift Keying): A digital modulation technique where the amplitude of the carrier wave is varied to represent binary data (e.g., 0 or 1).</li>
<li>FSK (Frequency Shift Keying): A digital modulation method where the frequency of the carrier wave is shifted between discrete values to represent binary data.</li>
</ul></li>
<li><p>Channel: A specific frequency band allocated for the transmission of a signal, ensuring separation from other signals</p></li>
<li><p>Sidelobe: Unintended emissions of power from a signal outside its main lobe, often caused by imperfections in transmission</p></li>
</ul>]]></content><author><name>Hendo</name></author><category term="CTF" /><category term="hacking" /><category term="radio" /><summary type="html"><![CDATA[In Feburary last year I attended Disobey in Finlandhttps://disobey.fi/2025/ , it was a great event, although very cold. At the event was a good CTF, with a bunch of physical challenges that were hosted in the building. I didn&#39;t spend too much time in the CTF, but in this post I&#39;m going to write up my failed attempt to solve one of the challenges.]]></summary></entry><entry><title type="html">Cheating</title><link href="/cheating/" rel="alternate" type="text/html" title="Cheating" /><published>2024-09-29T00:00:00-05:00</published><updated>2024-09-29T00:00:00-05:00</updated><id>/cheating</id><content type="html" xml:base="/cheating/"><![CDATA[<p>Lots of fiction, in all forms, focuses on the world of spying and espionage. There are thousands of books, films, and games that cover the subject, and from different angles. There are non-fiction books and documentaries that study the true history of the subject, romantic fiction, and all sorts in-between.</p>

<p>The most common stereotype among these is probably that of the action packed adventure: fast cars, gunfights, doomsday weapons. James Bond, Jason Bourne, Jack Ryan, all rush from danger to danger in exotic locales across the world.</p>

<p>Then there&#39;s another favourite subject of fiction and documentary: police procedurals, murder mysteries, true crime. FBI agents, police inspectors, and complete novices crack mysteries, piece together clues, and the mystery concludes with a dramatic gunfight with the villain, in abandoned fortresses, the faces of mount Rushmore, serial killer&#39;s basements.</p>

<p>But these are romanticised depictions. Instead, real mysteries are solved by sifting through information and painstakingly building a picture of the relationships between people and events. The same with espionage, more damage can be done by poring over serial numbers<label for='86PUy' class='margin-toggle sidenote-number'></label><input type='checkbox' id='86PUy' class='margin-toggle'/><span class='sidenote'><a href="https://www.numberphile.com/videos/clever-way-to-count-tanks">https://www.numberphile.com/videos/clever-way-to-count-tanks</a> </span> than with silenced pistols.</p>

<p>This is the idea behind &quot;A Hand with Many Fingers&quot;<label for='FlGFy' class='margin-toggle sidenote-number'></label><input type='checkbox' id='FlGFy' class='margin-toggle'/><span class='sidenote'><a href="https://store.steampowered.com/app/1229030/A_Hand_With_Many_Fingers/">https://store.steampowered.com/app/1229030/A_Hand_With_Many_Fingers/</a> </span>, a game that is inspired by another genre trope. It&#39;s a scene found in several places in espionage/mystery genre: the detective/spy is stumped and has no leads. Or they&#39;ve pissed off their bosses by overstepping a line. Either way, they&#39;re relegated to the basement archives to hunt for clues the hard way: poring through documents. Here&#39;s Rust Cohle at it, in True Detective. </p>

<p><img src="/assets/img/hand/rust.jpg" alt=""></p>

<p><br></p>

<p>There&#39;s often a corkboard and red string involved, and after a montage of looking tired, papercopying files and drinking coffee out of polystyrene cups, the hero is rewarded by a new connection. This is a plot device used to take a break from the action, act as an ordeal and Apotheosis in a character arc, to move the plot forward.</p>

<p>In &quot;A Hand with Many Fingers&quot; the premise is this: you are an un-named, un-described individual in the employ of an implied but undefined government agency (FBI?). You are granted a corkboard, some filing cabinets, and a basement archive store with hundreds of files. </p>

<figure><img src='/assets/img/hand/hand1.jpg'/><figcaption class='maincolumn-figure'>https://store.steampowered.com/app/1229030/A_Hand_With_Many_Fingers/</figcaption></figure>

<!-- ![](/assets/img/hand/hand1.jpg)
<label for='4hdFp' class='margin-toggle'> &#8853;</label><input type='checkbox' id='4hdFp' class='margin-toggle'/><span class='marginnote'>https://store.steampowered.com/app/1229030/A_Hand_With_Many_Fingers/ </span>
 -->

<p>You start with a single newspaper cutting: the death of a man in Australia by the name of Nugan Hand. From this single piece of information, and the archive files, you use the corkboard to unravel the connections and events surrounding Hand. You don&#39;t know what you&#39;re going to find, maybe you will discover the truth behind his death, or something else entirely. From a suspicious death, you trace Hand&#39;s connections, and it becomes apparent that Hand was closely linked to schemes such as Air America <label for='gce9m' class='margin-toggle sidenote-number'></label><input type='checkbox' id='gce9m' class='margin-toggle'/><span class='sidenote'><a href="https://en.wikipedia.org/wiki/Air_America_(airline)">https://en.wikipedia.org/wiki/Air_America_(airline)</a> </span>, was close friends with a former CIA director, and the story turns from an isolated death to global geopolitics, history, and espionage. </p>

<p>The game&#39;s mechanics are also pleasingly simple. You start from the one newspaper clipping. You can use information from that to extract a triplet of name, year, and country of interest. You then use this to consult the filing cabinets. There&#39;s a bank of cabinets for each region of the world, a drawer per-year, and an alphabetical list of names in each drawer. </p>

<p><img src="/assets/img/hand/hand_drawers.jpg" alt=""></p>

<p><br><br></p>

<p>You go to the drawers, lookup the information by year and name, and are given a card with several numbers on it. The numbers are in turn the numbers of boxes, stored in the basement. You trundle down to the basement, retrieve the numbered boxes and examine the contents. The contents will be more newspaper clippings, or redacted intelligence reports, or torn bank statements. These may reveal more dates, names, and places, and you continue the hunt. On your corkboard you can pin these clippings, and draw pleasing red strings between them to show connections.
<br><br></p>

<p><img src="/assets/img/hand/process.png" alt=""></p>

<p>Slowly you build a network of connections, and begin to understand the relationships between the key characters, and their shady dealings across Vietnam, Angola, and the middle east. At this point it&#39;s worth mentioning that this is based on real events, although official collusion between Nugan Hand Bank and the CIA is disputed<label for='I4Pf0' class='margin-toggle sidenote-number'></label><input type='checkbox' id='I4Pf0' class='margin-toggle'/><span class='sidenote'><em>Warning: spoilers for the game</em>: <a href="https://en.wikipedia.org/wiki/Nugan_Hand_Bank">https://en.wikipedia.org/wiki/Nugan_Hand_Bank</a> </span>. The characters and events depicted in the game were the real subject of conspiracy, and investigation. Nugan Hand Bank was ultimately found guilty of fraud, money laundering, and funding drug smuggling, but the connections to arms smuggling for the CIA remain unproven allegations.</p>

<p>That method of manually hunting down and cross referencing dates is a great way of drawing you into the conspiracy: you slowly uncover new links and start thinking about new theories as to why characters are really connected. But what if there was another way of solving the mystery? To do this we&#39;re going to look at a mathematical technique for analysing information, used by real world spies, investigators, and private surveillance organisations. The technique is Social Network Analysis (SNA). Not &quot;social network&quot; as in social media, alhough there is a very close relationship between the two, but the technique of analysing connections that represent &quot;social&quot; ie human relationships, and &quot;network&quot;s in the sense of groups of people.</p>

<p><strong>Social Network Analysis</strong> is an application of graph theory <label for='XhT8R' class='margin-toggle sidenote-number'></label><input type='checkbox' id='XhT8R' class='margin-toggle'/><span class='sidenote'><a href="https://en.wikipedia.org/wiki/Graph_theory">https://en.wikipedia.org/wiki/Graph_theory</a> </span>, which represents data in a Graph. There are Nodes, and edges which link them. These can be used to represent whatever you want, it could be roads between points on a map, abstract mathematical concepts, or in the case of social network analysis: people and the connections between them. In fact social network analysis doesn&#39;t just have to be people, it could be countries, political movements, etc. But the theories are mostly applicable to people, or things that act &quot;like&quot; people, such as groups of people.</p>

<p>The beauty of social network analysis is how general purpose it is: it can give fascinating insights into people and behaviour from seemingly nonsensical data. There&#39;s no particular question it can answer, but instead, given some input data it will assign &quot;weights&quot; and patterns in the data that a human being can interpret.  The algorithms are in fact completely ignorant of the &quot;meaning&quot; of data, the techniques operate purely on numbers, and it&#39;s humans that supply the context of input, and the resulting interpretation. This gives incredible flexibility: if your input data shows text messages between people, you can see social structures drawn out and draw conclusions about friendships, and relationships. but if you change that dataset to who speaks to each other in person, you get a different output, and different conclusions.</p>

<h2 id="introduction-to-graph-theory">Introduction to graph theory</h2>

<p>Let&#39;s start off with a classic example: a graph between characters. We start off with our lists of people, and the strenght of their relationship on a scale from 0-10.</p>

<table><thead>
<tr>
<th>Character A</th>
<th>Character B</th>
<th>Weight</th>
</tr>
</thead><tbody>
<tr>
<td>Acciaiuoli</td>
<td>Medici</td>
<td>7</td>
</tr>
<tr>
<td>Medici</td>
<td>Barbadori</td>
<td>8</td>
</tr>
<tr>
<td>Medici</td>
<td>Ridolfi</td>
<td>9</td>
</tr>
<tr>
<td>Medici</td>
<td>Tornabuoni</td>
<td>9</td>
</tr>
<tr>
<td>Medici</td>
<td>Albizzi</td>
<td>9</td>
</tr>
<tr>
<td>Medici</td>
<td>Salviati</td>
<td>8</td>
</tr>
<tr>
<td>Castellani</td>
<td>Peruzzi</td>
<td>6</td>
</tr>
<tr>
<td>Castellani</td>
<td>Strozzi</td>
<td>7</td>
</tr>
<tr>
<td>Castellani</td>
<td>Barbadori</td>
<td>5</td>
</tr>
<tr>
<td>Peruzzi</td>
<td>Strozzi</td>
<td>7</td>
</tr>
<tr>
<td>Peruzzi</td>
<td>Bischeri</td>
<td>6</td>
</tr>
<tr>
<td>Strozzi</td>
<td>Ridolfi</td>
<td>7</td>
</tr>
<tr>
<td>Strozzi</td>
<td>Bischeri</td>
<td>7</td>
</tr>
<tr>
<td>Ridolfi</td>
<td>Tornabuoni</td>
<td>6</td>
</tr>
<tr>
<td>Tornabuoni</td>
<td>Guadagni</td>
<td>7</td>
</tr>
<tr>
<td>Albizzi</td>
<td>Ginori</td>
<td>4</td>
</tr>
<tr>
<td>Albizzi</td>
<td>Guadagni</td>
<td>7</td>
</tr>
<tr>
<td>Salviati</td>
<td>Pazzi</td>
<td>3</td>
</tr>
<tr>
<td>Bischeri</td>
<td>Guadagni</td>
<td>7</td>
</tr>
<tr>
<td>Guadagni</td>
<td>Lamberteschi</td>
<td>5</td>
</tr>
</tbody></table>

<p>Now we draw a diagram, with lines between characters, if they have a relationship. We&#39;ve already done some simple network analysis here, just by depicting relationships. Suddenly out of a list of names we have shape and pattern, we can begin to see meaning in this. One of the most common forms of analysis is of &quot;cliques&quot; and &quot;clusters&quot; <label for='I7KZX' class='margin-toggle sidenote-number'></label><input type='checkbox' id='I7KZX' class='margin-toggle'/><span class='sidenote'><a href="https://www.oreilly.com/library/view/social-network-analysis/9781449311377/ch04.html">https://www.oreilly.com/library/view/social-network-analysis/9781449311377/ch04.html</a> (you can find the full version online) </span>. Graphs of real world data are not uniformly distributed, and instead we can in fact make out sub-groups, or clusters. These can be connected, or entirely separate, with no clusters. again, it&#39;s up to humans to provide interpretation onto this, but if we take graphs of nodes that represent people, these clusters are likely to show friendships, relationships, families etc.</p>

<div style="text-align: center;">
<iframe src="/assets/img/hand/florentine_unweighted.html" width="800" height="500" style="border:none; background:transparent;" frameborder="0"></iframe>
</div>

<p>These can be aligned with other attributes people have, such as politics, religion, nationality, interests, beliefs. Suddenly you can see why spies and companies such as Cambridge Analytica <label for='cI0Br' class='margin-toggle sidenote-number'></label><input type='checkbox' id='cI0Br' class='margin-toggle'/><span class='sidenote'><a href="https://en.wikipedia.org/wiki/Cambridge_Analytica#Methods">https://en.wikipedia.org/wiki/Cambridge_Analytica#Methods</a> </span> are so interested in the subject, and where all of Meta&#39;s profits come from.</p>

<p>Now let&#39;s do something else, instead of a simple graph with nodes and edges, let&#39;s depict some &quot;weights&quot;. Weights are where we assign a value to edges or nodes. Again, this number can represent whatever we want it to. In our example, we&#39;ll use the relationship score as our weight for edges. Now let&#39;s draw the graph again, and visualise the edge weights by size.</p>

<div style="text-align: center;">
<iframe src="/assets/img/hand/florentine_weighted.html" width="800" height="500" style="border:none; background:transparent;" frameborder="0"></iframe>
</div>

<p>We&#39;ve got the same graph as before, but there&#39;s even more information. Now we can see that not only are characters connected, but some are more connected than others. If we supply our human interpretation to the cold numbers again, we can think of these weights as the strength of a relationship. </p>

<h2 id="centrality">CENTRALITY</h2>

<p>And now we&#39;re going to dive into even more interesting territory: the concept of centrality. CENTRALITY<label for='Jj5vr' class='margin-toggle sidenote-number'></label><input type='checkbox' id='Jj5vr' class='margin-toggle'/><span class='sidenote'><a href="https://www.oreilly.com/library/view/social-network-analysis/9781449311377/ch03.html">https://www.oreilly.com/library/view/social-network-analysis/9781449311377/ch03.html</a> </span> is a function in graph theory that has significant implications in the world of social network analysis. Centrality, as the name implies, is a measure of how &quot;central&quot; a node is. Consider the following graph:</p>

<div align="center">

<img src="/assets/img/hand/graph_simple_1.png">

</div>

<p><br>
What&#39;s the most &quot;central&quot; / important node? clearly the one surrounded by the 3 other nodes: node A. &quot;Important&quot; here is a really vaguely defined concept, and this comes back to the idea earlier: graph theory has no actual knowledge of the &quot;meaning&quot; of data, it just crunches numbers. Importance is a concept superimposed onto these random shapes by people.</p>

<p>In fact in the above diagram there&#39;s no indicator as to why any of these nodes in particular is &quot;important&quot; in any real sense, we have no way of knowing what they represent, but we can measure centrality. We could sort of define importance as a synonym for centrality by thinking &quot;How much would it disrupt the graph if this node was removed?&quot;. We can see here that if we removed any one of the outside nodes we&#39;d still be left with most of the graph. But the second we remove the centre we no longer have a graph, just some scattered and disconnected dots. </p>

<p>Just to be clear, centrality isn&#39;t anything to do with placement or spacing, while we are drawing the nodes here on a 2 dimensional plane, that&#39;s just a visual representation to conceptualise the idea: the nodes have no inherent property of x,y coordinates. Node A is still the most &quot;central&quot; to this graph, regardless of how we visually represent it:</p>

<figure><img src='/assets/img/hand/graph_simple_2.png'/><figcaption class='maincolumn-figure'>No matter which way we re-arrange the nodes, A is still more central than the others, due to the connections</figcaption></figure>

<p>Ok, so how is centrality defined? we can see in the examples above, and as humans consider it sort of obvious, but what about a more complex example. What are the centralities of nodes in this graph?</p>

<iframe src="/assets/img/hand/complex.html" width="100%" height="600" style="border:none; background:transparent;" frameborder="0"></iframe>

<p>If we go back to our simple example, why was node A the most central? Well if we take each node, and assign it a number based on the number of edges it has, we can see this gives us our result, where the middle node has a centrality of 3, and the others 1:</p>

<div align="center">
<img src="/assets/img/hand/graph_simple_1_degree.png">
</div>

<p><br><br></p>

<p>This is the first measure of centrality, known as DEGREE CENTRALITY: the centrality of a node is the number of edges it has<label for='neI81' class='margin-toggle sidenote-number'></label><input type='checkbox' id='neI81' class='margin-toggle'/><span class='sidenote'><a href="https://www.sciencedirect.com/topics/computer-science/degree-centrality">https://www.sciencedirect.com/topics/computer-science/degree-centrality</a> </span>. This is a pleasingly simple measure, and a building block of other forms. There are other common techniques for assigning centrality, but we&#39;ll only look at one: EIGENVECTOR CENTRALITY<label for='ypdWC' class='margin-toggle sidenote-number'></label><input type='checkbox' id='ypdWC' class='margin-toggle'/><span class='sidenote'><a href="https://en.wikipedia.org/wiki/Eigenvector_centrality">https://en.wikipedia.org/wiki/Eigenvector_centrality</a> </span>. This is probably one of the most commonly used and important measures. The maths is more complex than I really understand, but we can say the way it works is that it takes into account the relative centralities of the neighbours of each node. This means that centrality&quot; flows&quot; through the graph. This results in it disproportionately picking out particular clusters of high importance.</p>

<p>This measure seems to have several impactful consequences in social networks, for example, think about if we use the idea of centrality as an indicator of &quot;importance&quot; or &quot;influence&quot; among people. let&#39;s say <strong>Alice</strong> influences 10 people (10 edges), and <strong>Bob</strong> also influences 10 people. who&#39;s more influential? Degree centrality would say they&#39;re equally important. But eigenvector centrality would calculate scores based on the influence that each of <strong>Alice</strong> or <strong>Bob</strong>&#39;s connections have. If all <strong>Bob</strong>&#39;s friends are nobodies who don&#39;t know anyone (they have 0 connections, other than to <strong>Bob</strong>) then really how much influence does he have? But if all <strong>Alice</strong>&#39;s 10 connections are celebrities and powerful politicians, who in turn influence thousands of other people, then <strong>Alice</strong>&#39;s ultimate influence is greater. EIGENVECTOR centrality would assign <strong>Alice</strong> a much much higher score than <strong>Bob</strong>.</p>

<p>We can briefly see the difference between degree and eigenvector centralities in these renderings, showing the exact same data marked using different centrality techniques.</p>

<figure><img src='/assets/img/hand/centrality.png'/><figcaption class='maincolumn-figure'>https://commons.wikimedia.org/wiki/File:Wp-01.png</figcaption></figure>

<p>There&#39;s no &quot;one&quot; interpretation of centrality when applied to social networks, but we often associate the amount of connections people have with power and influence: think about the phrase &quot;well connected&quot;. Maybe it&#39;s the number of people who&#39;d lend you money, vote for you, tell you information they shouldn&#39;t, but the point is, it&#39;s generally regarded as beneficial. So with people we can think of centrality as a measure of social power / influence. In a network representing people, we can often think of high centrality as a representation of leadership, or control of a group. There&#39;s a fantastic example (that inspired this post) of showing how social network analysis and centrality could be applied to information from 1775 to identify the ringleader(s) of the American revolution: </p>

<ul>
<li><a href="http://euler.nmt.edu/%7Ebrian/revere.pdf">http://euler.nmt.edu/~brian/revere.pdf</a></li>
<li><a href="https://kieranhealy.org/blog/archives/2013/06/09/using-metadata-to-find-paul-revere/">https://kieranhealy.org/blog/archives/2013/06/09/using-metadata-to-find-paul-revere/</a></li>
</ul>

<p>If the British counter-revolutionary police had known of this technique then, history might look different. And this shows one of the main uses of SNA: identifying important groups and leadership structures from data.  It doesn&#39;t take much imagination to work out who&#39;s using this and what for.</p>

<p>Before we finish discussing SNA there&#39;s something important to point out. Earlier I said the beauty of the idea is that there&#39;s no one interpretation of the outcome, the algorithms only input and output numbers, only humans deal in &quot;meaning&quot;. There&#39;s a flip-side to this as well: as there&#39;s no one answer or interpretation of the data, and no way of knowing if the interpretation you&#39;ve placed on the data is correct. The maths draws connections that might not have any real-world relevance, or the meaning of which is hard to interpret. the connection between a sibling, or a couple look the same to the maths, but we as humans know those are very different types of relationships. Making definitive judgements on the meaning of the data can be hard, and is fraught with risk. It can be tempting to impose simple, pleasing conclusions on the data, and as we know from examples like the birthday paradox, humans are bad at processing probability, and what might sound unlikely can occur much more frequently than we think. In short, jumping to conclusions because SNA makes something appear a certain way isn&#39;t a great idea.</p>

<h2 id="applying-social-network-analysis">Applying Social Network Analysis</h2>

<p>Ok, so back from the mathematical sojourn: why do we care about SNA / centrality when it comes to &quot;A Hand with many fingers&quot;? Well if we think about it, the game&#39;s central puzzle is this: here&#39;s 100 filing cabinet&#39;s worth of unstructured data on people, and the player&#39;s job is to find the pattern between them. </p>

<p>So what if we take this information, and instead of actually solving the puzzle in-game, just find a way of representing it as a social network, and seeing what the data shows us? Can we use SNA to find the network of people connected to Hand?</p>

<p>Remember that the game works as follows: </p>

<ul>
<li>we have banks of filing cabinets, one per region</li>
<li>we have a drawer in each bank for each year</li>
<li>each drawer contains a list of cards with names</li>
<li>each card has a reference to one or more references to files in the basement archives</li>
</ul>

<p>As in our earlier examples, we need to pick what our nodes will be. This is simple, each person in the game is represented as a surname on a card index. Now we need to pick relationships, and this is more complex. Several potential definitions immediately come to mind:</p>

<ul>
<li>option 1: create a connection between two people if they have been to the same region ever. In the game this means if their names are in the same bank of cabinets.</li>
<li>option 2: create a connection between two people if they were in the same region in the same year. In the game this means the two names would appear in the same drawer</li>
<li>option 3: create a connection between two people if they are both referenced in the same file. This would mean both their names share a file index.</li>
</ul>

<p>Let&#39;s dismiss the first: the game has 8 regions, which isn&#39;t really a rich enough dataset. We&#39;re likely to just end up with a graph that&#39;s either just 8 clusters of each region, or just a complete mess. This might tell us who amongst the group is the most frequent traveller, but if 2 people have been to the same region somewhere in a 10 year period, that doesn&#39;t mean much, especially considering the regions are often entire continents.</p>

<p>The second, create a connection between two people if they were in the same region in the same year, is immediately more interesting, as it&#39;s effectively how you&#39;re making connections at the start of the game. You have a newspaper clipping with Hand, Australia, 1979, and start looking for who else was in the region at the same time. </p>

<p>The third is almost better: if two people are mentioned in the same document (news article, photo, bank transfer), chances are there&#39;s a real, tangible connection, and our graph should be very powerful as a result.</p>

<p>The last two are almost equally attractive, so let&#39;s start with these. But before we can start trying out our theory we have to actually gather the data needed. </p>

<h2 id="collecting-data">Collecting data</h2>

<p>I could sit in the game for several hours, and manually copy the data out into a spreadsheet, but that sounds time consuming, so let&#39;s cheat. If the information is in the game, it has to exist somewhere in the game&#39;s files. So let&#39;s use the venerable strings.exe<label for='kBiJ5' class='margin-toggle sidenote-number'></label><input type='checkbox' id='kBiJ5' class='margin-toggle'/><span class='sidenote'><a href="https://learn.microsoft.com/en-us/sysinternals/downloads/strings">https://learn.microsoft.com/en-us/sysinternals/downloads/strings</a> </span> to take a peek at the files. The first thing I&#39;m going to do is take the name &quot;ANDERSON&quot; and search in the games files for this data. It&#39;s worth pointing out I&#39;ve changed the name &quot;ANDERSON&quot; from what it is in the game, to not spoil the plot. When we get the data and graph it, I&#39;ve also switched every name in the game for another, to avoid spoilers (although they&#39;re real names, findable on Wikipedia, so go figure...). The reason I didn&#39;t use the name &quot;Hand&quot; that we already knew, is that it&#39;s only 4 characters, and shows up in the name of the game. Searching for strings less than 5 characters in unsorted binary data is likely to find false positives. To be honest this isn&#39;t actually a big problem, but would be on larger files: the likelihood of a completely random 4 byte sequence spelling the ASCII for &quot;Hand&quot; is 1/256^4: 4294967296 a  ~ 3GB file (3221225472 bytes) has approximately X% chance of that cropping up. The real reason for not picking &quot;Hand&quot; is that it&#39;s in the name of the game, and therefore probably crops up in all sorts of metadata / asset data / class names that we might find in the files.</p>

<p>Anyway, we search for &quot;ANDERSON&quot;, and something immediately pops up:</p>
<div class="highlight"><pre><code class="language-" data-lang="">PS &gt; strings.exe -n 10 'A Hand With Many Fingers_Data\level1' | select-string anderson

ME-1978-ANDERSON
AU-1980-ANDERSON
Text-ANDERSONFlights N
BC-ANDERSON
Text-BakerANDERSON1978
Text-WilsonANDERSON1975
Photo-BernieANDERSON N
ANDERSON. B

</code></pre></div>
<p>Fab, this looks exactly like the data we&#39;re looking for: we see refereneces to regions (ME = Middle East, AU = australasia), and years. Now just to check, let&#39;s search for a random name that&#39;s not connected to the plot, to make sure it&#39;s there. The name &quot;gentry&quot; should work, their name appears in some files, but has no relevance to the story.</p>
<div class="highlight"><pre><code class="language-" data-lang="">
PS &gt; strings.exe -n 10 'A Hand With Many Fingers_Data\level1' | select-string gentry
PS &gt;

</code></pre></div>
<p>Uh-oh. No hits for &quot;gentry&quot; in that file where we found the &quot;anderson&quot; references. This suggests the file we&#39;re searching only contains the plot-relvant names. In other words: a name in this file means they appear in the &quot;main story&quot;, and if it doesn&#39;t, it&#39;s a random entity used as filler. This would allow us to work out the main story actors by simply seeing if they exist in this file. That&#39;s cool, but if we put ourselves in the shoes of the unnamed detetive in the archive, we wouldn&#39;t have this information, and so it&#39;s extra-cheatey. Let&#39;s keep looking.</p>

<p>The second file that jumps out looks much more promising: This looks exactly like the definition of the filing cabinets, wrapped in JSON.</p>
<div class="highlight"><pre><code class="language-" data-lang="">PS &gt; strings.exe -n 5 'A Hand With Many Fingers_Data\sharedassets1.assets' | select-string gentry

GENTRY
[{"surname":"ABBOTT\r","initial":"E","refs":["OS 654/49","OS
331/91","OS 272/94"]},{"surname":"ADAMS\r","initial":"O","refs":["OS
225/85","OS 472/82","OS
... SNIPPED ...

667/30","OS 682/65"]},{"surname":"ZAMORA","initial":"W","refs":["OS
236/56","OS
314/24"]}]


</code></pre></div>
<p>If we unpick this we get a JSON list with entries such as the following:</p>
<div class="highlight"><pre><code class="language-json" data-lang="json"><span class="p">{</span><span class="w">
    </span><span class="nl">"surname"</span><span class="p">:</span><span class="w"> </span><span class="s2">"CARSON</span><span class="se">\r</span><span class="s2">"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"initial"</span><span class="p">:</span><span class="w"> </span><span class="s2">"D"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"refs"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
      </span><span class="s2">"OS 370/8"</span><span class="w">
    </span><span class="p">]</span><span class="w">
  </span><span class="p">}</span><span class="err">,</span><span class="w">

</span></code></pre></div>
<p>That&#39;s a bit confusing, there&#39;s no grouping there of &quot;region&quot;, or even year, just names and their card index references. So this only gives enough data for method 3: linking two people if they share a card reference. Unforetunately, this has the opposite problem as the first example, this contains every entry except the key characters, so back to the drawing board. At theis point I tried a few more approaches:</p>

<ul>
<li>searching for the reference numbers themselves in files, to see if any file contains both story critical and non-critical references. Nope.</li>
<li>trying to recreate the above JSON structure of the non-story entries found above by recreating data from the <code>level1</code> file. Unfortunately this is also too hard: the binary structure doesn&#39;t seem to keep OS numbers next to story character names, so can&#39;t automatically link them. You can see an example of a file opened in a hex editor below showing this</li>
<li>trying to look not in the files, but in the game&#39;s memory at runtime, as maybe the data is rearranged at runtime. Nope.</li>
</ul>

<p><img src="/assets/img/hand/hand4.png" alt="">
<br>
At this point I&#39;m about to give up, and trawl through every filing cabinet in the game to gather the information, but then I found a guide on the steam guides page for the game, that lists the story relevant card entries. For example, we now have information such as <code>Australia 1980 - Hand: OS 633/75, OS 385/14</code>. I&#39;ll adapt this to be a mapping between (sur)names and OS numbers, and we get:</p>
<div class="highlight"><pre><code class="language-" data-lang="">Hand: [OS 633/75, OS 385/14, OS 385/14, OS 385/14, OS 449/13, OS 385/14, OS 590/7, OS 385/14, OS 449/13
Nugan: OS 109/72, OS 633/75, 
Baker: OS 161/15, OS 615/53, OS 449/13, OS 161/15, OS 786/95
REEDER: OS 633/75, OS 802/84, OS 161/15, OS 802/84
Collins: OS 488/17
Martello: OS 267/4, OS 536/84

</code></pre></div>
<p><label for='XvWGJ' class='margin-toggle'> &#8853;</label><input type='checkbox' id='XvWGJ' class='margin-toggle'/><span class='marginnote'>again, I\&#39;ve obfuscated the original names here </span></p>

<p>There&#39;s a lot of duplication, since each box might contain multiple files, which reference other dates and places, this is the point of an archiving system like this. This doesn&#39;t matter for now. Let&#39;s store the data as a dict of sets, so we get a key-value mapping names to a list of OS numbers. If we also adapt the non-story examples to this format we should have a good enough datasource to work from:</p>
<div class="highlight"><pre><code class="language-python" data-lang="python"><span class="c1">#!/usr/bin/env python3
</span>
<span class="kn">import</span> <span class="nn">json</span>
<span class="kn">import</span> <span class="nn">networkx</span> <span class="k">as</span> <span class="n">nx</span>

<span class="n">data_dict</span> <span class="o">=</span> <span class="p">{}</span>

<span class="n">story_data</span> <span class="o">=</span> <span class="p">{</span>
    <span class="s">"HAND"</span><span class="p">:</span> <span class="p">[</span><span class="s">"OS 633/75"</span><span class="p">,</span> <span class="s">"OS 385/14"</span><span class="p">,</span> <span class="s">"OS 385/14"</span><span class="p">,</span> <span class="s">"OS 385/14"</span><span class="p">,</span> <span class="s">"OS 449/13"</span><span class="p">,</span> <span class="s">"OS 385/14"</span><span class="p">,</span> <span class="s">"OS 590/7"</span><span class="p">,</span> <span class="s">"OS 385/14"</span><span class="p">,</span> <span class="s">"OS 449/13"</span><span class="p">],</span>
    <span class="s">"NUGAN"</span><span class="p">:</span> <span class="p">[</span><span class="s">"OS 109/72"</span><span class="p">,</span> <span class="s">"OS 633/75"</span><span class="p">,</span> <span class="p">],</span>
    <span class="s">"Baker"</span><span class="p">:</span> <span class="p">[</span><span class="s">"OS 161/15"</span><span class="p">,</span> <span class="s">"OS 615/53"</span><span class="p">,</span> <span class="s">"OS 449/13"</span><span class="p">,</span> <span class="s">"OS 161/15"</span><span class="p">,</span> <span class="s">"OS 786/95"</span><span class="p">],</span>
    <span class="s">"REEDER"</span><span class="p">:</span> <span class="p">[</span><span class="s">"OS 633/75"</span><span class="p">,</span> <span class="s">"OS 802/84"</span><span class="p">,</span> <span class="s">"OS 161/15"</span><span class="p">,</span> <span class="s">"OS 802/84"</span><span class="p">],</span>
    <span class="s">"Collins"</span><span class="p">:</span> <span class="p">[</span><span class="s">"OS 488/17"</span><span class="p">],</span>
    <span class="s">"Martello"</span><span class="p">:</span> <span class="p">[</span><span class="s">"OS 267/4"</span><span class="p">,</span> <span class="s">"OS 536/84"</span><span class="p">],</span>
<span class="p">}</span>

<span class="k">for</span> <span class="n">name</span><span class="p">,</span><span class="n">numbers</span> <span class="ow">in</span> <span class="n">story_data</span><span class="p">.</span><span class="n">items</span><span class="p">:</span>
    <span class="n">data_dict</span><span class="p">[</span><span class="n">name</span><span class="p">]:</span> <span class="nb">set</span><span class="p">(</span><span class="n">numbers</span><span class="p">)</span>

<span class="n">non_story_data</span> <span class="o">=</span> <span class="n">json</span><span class="p">.</span><span class="n">load</span><span class="p">(</span><span class="nb">open</span><span class="p">(</span><span class="s">'hand.json'</span><span class="p">,</span> <span class="s">'r'</span><span class="p">))</span>

<span class="k">for</span> <span class="n">d</span> <span class="ow">in</span> <span class="n">non_story_data</span><span class="p">:</span>
    <span class="k">if</span> <span class="n">d</span><span class="p">[</span><span class="s">'surname'</span><span class="p">]</span> <span class="ow">in</span> <span class="n">data_dict</span><span class="p">:</span>
        <span class="n">data_dict</span><span class="p">[</span><span class="n">d</span><span class="p">[</span><span class="s">'surname'</span><span class="p">]].</span><span class="n">update</span><span class="p">([</span><span class="n">d</span><span class="p">[</span><span class="s">'refs'</span><span class="p">]])</span>
    <span class="k">else</span><span class="p">:</span>
        <span class="n">data_dict</span><span class="p">[</span><span class="n">d</span><span class="p">[</span><span class="s">'surname'</span><span class="p">]]</span> <span class="o">=</span> <span class="nb">set</span><span class="p">([</span><span class="n">d</span><span class="p">[</span><span class="s">'refs'</span><span class="p">]])</span>

<span class="c1"># data_dict now contains all OS refs
</span>
</code></pre></div>
<p>Here&#39;s some simple python to parse the data, create a graph, caclulating degree and eigenvector centrality, using the networkX library:</p>

<div class="dropdown-code">
  <button class="dropdown-code-toggle" onclick="toggleDropdownCode('dropdown-95691')" aria-expanded="false">
    <span class="dropdown-arrow">▶</span>
    <span class="dropdown-title">hand.py</span>
  </button>
  <div id="dropdown-95691" class="dropdown-code-content" hidden>
    <div class="highlight"><pre><code class="language-python" data-lang="python"><span class="c1">#!/usr/bin/env python3
</span>
<span class="kn">import</span> <span class="nn">json</span>
<span class="kn">import</span> <span class="nn">pandas</span> <span class="k">as</span> <span class="n">pd</span>
<span class="kn">import</span> <span class="nn">networkx</span> <span class="k">as</span> <span class="n">nx</span>
<span class="kn">import</span> <span class="nn">matplotlib.pyplot</span> <span class="k">as</span> <span class="n">plt</span>
<span class="kn">from</span> <span class="nn">bokeh.models</span> <span class="kn">import</span> <span class="n">Range1d</span><span class="p">,</span> <span class="n">Circle</span><span class="p">,</span> <span class="n">ColumnDataSource</span><span class="p">,</span> <span class="n">MultiLine</span><span class="p">,</span> <span class="n">LabelSet</span><span class="p">,</span> <span class="n">CustomJS</span>
<span class="kn">from</span> <span class="nn">bokeh.transform</span> <span class="kn">import</span> <span class="n">linear_cmap</span>
<span class="kn">from</span> <span class="nn">bokeh</span> <span class="kn">import</span> <span class="n">palettes</span>
<span class="kn">from</span> <span class="nn">bokeh.io</span> <span class="kn">import</span> <span class="n">show</span><span class="p">,</span> <span class="n">output_file</span>
<span class="kn">from</span> <span class="nn">bokeh.plotting</span> <span class="kn">import</span> <span class="n">figure</span><span class="p">,</span> <span class="n">from_networkx</span>

<span class="k">def</span> <span class="nf">build_centrality_table</span><span class="p">(</span><span class="n">G</span><span class="p">):</span>

    <span class="s">'''
    given a graph G and a centrality metric, return dataframe of node centralities :)
    '''</span>

    <span class="k">try</span><span class="p">:</span>
        <span class="n">df</span> <span class="o">=</span> <span class="n">pd</span><span class="p">.</span><span class="n">DataFrame</span><span class="p">.</span><span class="n">from_dict</span><span class="p">(</span><span class="n">nx</span><span class="p">.</span><span class="n">closeness_centrality</span><span class="p">(</span><span class="n">G</span><span class="p">),</span> <span class="n">orient</span><span class="o">=</span><span class="s">'index'</span><span class="p">,</span> <span class="n">columns</span><span class="o">=</span><span class="p">[</span><span class="s">'closeness_centrality'</span><span class="p">])</span>
        <span class="n">df</span> <span class="o">=</span> <span class="n">pd</span><span class="p">.</span><span class="n">merge</span><span class="p">(</span><span class="n">df</span><span class="p">,</span><span class="n">pd</span><span class="p">.</span><span class="n">DataFrame</span><span class="p">.</span><span class="n">from_dict</span><span class="p">(</span><span class="n">nx</span><span class="p">.</span><span class="n">degree_centrality</span><span class="p">(</span><span class="n">G</span><span class="p">),</span> <span class="n">orient</span><span class="o">=</span><span class="s">'index'</span><span class="p">,</span> <span class="n">columns</span><span class="o">=</span><span class="p">[</span><span class="s">'degree_centrality'</span><span class="p">]),</span> <span class="n">left_index</span><span class="o">=</span><span class="bp">True</span><span class="p">,</span> <span class="n">right_index</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span>
        <span class="n">df</span> <span class="o">=</span> <span class="n">pd</span><span class="p">.</span><span class="n">merge</span><span class="p">(</span><span class="n">df</span><span class="p">,</span><span class="n">pd</span><span class="p">.</span><span class="n">DataFrame</span><span class="p">.</span><span class="n">from_dict</span><span class="p">(</span><span class="n">nx</span><span class="p">.</span><span class="n">eigenvector_centrality</span><span class="p">(</span><span class="n">G</span><span class="p">,</span> <span class="n">max_iter</span><span class="o">=</span><span class="mi">10000</span><span class="p">),</span> <span class="n">orient</span><span class="o">=</span><span class="s">'index'</span><span class="p">,</span> <span class="n">columns</span><span class="o">=</span><span class="p">[</span><span class="s">'eigenvector_centrality'</span><span class="p">]),</span> <span class="n">left_index</span><span class="o">=</span><span class="bp">True</span><span class="p">,</span> <span class="n">right_index</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span>
        <span class="n">df</span> <span class="o">=</span> <span class="n">pd</span><span class="p">.</span><span class="n">merge</span><span class="p">(</span><span class="n">df</span><span class="p">,</span><span class="n">pd</span><span class="p">.</span><span class="n">DataFrame</span><span class="p">.</span><span class="n">from_dict</span><span class="p">(</span><span class="n">nx</span><span class="p">.</span><span class="n">katz_centrality</span><span class="p">(</span><span class="n">G</span><span class="p">,</span> <span class="n">max_iter</span><span class="o">=</span><span class="mi">100000</span><span class="p">),</span> <span class="n">orient</span><span class="o">=</span><span class="s">'index'</span><span class="p">,</span> <span class="n">columns</span><span class="o">=</span><span class="p">[</span><span class="s">'katz_centrality'</span><span class="p">]),</span> <span class="n">left_index</span><span class="o">=</span><span class="bp">True</span><span class="p">,</span> <span class="n">right_index</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span>
    <span class="k">except</span> <span class="nb">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
        <span class="k">print</span><span class="p">(</span><span class="s">"Unable to calculate centralities"</span><span class="p">)</span>
        <span class="c1">#raise e
</span>        <span class="n">empty_centralities</span> <span class="o">=</span> <span class="p">{</span><span class="n">x</span><span class="p">:</span><span class="mi">0</span> <span class="k">for</span> <span class="n">x</span> <span class="ow">in</span> <span class="n">G</span><span class="p">.</span><span class="n">nodes</span><span class="p">}</span>
        <span class="n">df</span> <span class="o">=</span> <span class="n">pd</span><span class="p">.</span><span class="n">DataFrame</span><span class="p">.</span><span class="n">from_dict</span><span class="p">(</span><span class="n">empty_centralities</span><span class="p">,</span> <span class="n">orient</span><span class="o">=</span><span class="s">'index'</span><span class="p">,</span> <span class="n">columns</span><span class="o">=</span><span class="p">[</span><span class="s">'closeness_centrality'</span><span class="p">])</span>
        <span class="n">df</span> <span class="o">=</span> <span class="n">pd</span><span class="p">.</span><span class="n">merge</span><span class="p">(</span><span class="n">df</span><span class="p">,</span><span class="n">pd</span><span class="p">.</span><span class="n">DataFrame</span><span class="p">.</span><span class="n">from_dict</span><span class="p">(</span><span class="n">empty_centralities</span><span class="p">,</span> <span class="n">orient</span><span class="o">=</span><span class="s">'index'</span><span class="p">,</span> <span class="n">columns</span><span class="o">=</span><span class="p">[</span><span class="s">'eigenvector_centrality'</span><span class="p">]),</span> <span class="n">left_index</span><span class="o">=</span><span class="bp">True</span><span class="p">,</span> <span class="n">right_index</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span>
        <span class="n">df</span> <span class="o">=</span> <span class="n">pd</span><span class="p">.</span><span class="n">merge</span><span class="p">(</span><span class="n">df</span><span class="p">,</span><span class="n">pd</span><span class="p">.</span><span class="n">DataFrame</span><span class="p">.</span><span class="n">from_dict</span><span class="p">(</span><span class="n">empty_centralities</span><span class="p">,</span> <span class="n">orient</span><span class="o">=</span><span class="s">'index'</span><span class="p">,</span> <span class="n">columns</span><span class="o">=</span><span class="p">[</span><span class="s">'katz_centrality'</span><span class="p">]),</span> <span class="n">left_index</span><span class="o">=</span><span class="bp">True</span><span class="p">,</span> <span class="n">right_index</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span>
        <span class="n">df</span> <span class="o">=</span> <span class="n">pd</span><span class="p">.</span><span class="n">merge</span><span class="p">(</span><span class="n">df</span><span class="p">,</span><span class="n">pd</span><span class="p">.</span><span class="n">DataFrame</span><span class="p">.</span><span class="n">from_dict</span><span class="p">(</span><span class="n">empty_centralities</span><span class="p">,</span> <span class="n">orient</span><span class="o">=</span><span class="s">'index'</span><span class="p">,</span> <span class="n">columns</span><span class="o">=</span><span class="p">[</span><span class="s">'degree_centrality'</span><span class="p">]),</span> <span class="n">left_index</span><span class="o">=</span><span class="bp">True</span><span class="p">,</span> <span class="n">right_index</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span>

        <span class="c1"># return pn.widgets.DataFrame(pd.DataFrame())
</span>
    <span class="k">print</span><span class="p">(</span><span class="n">df</span><span class="p">.</span><span class="n">sort_values</span><span class="p">(</span><span class="s">'eigenvector_centrality'</span><span class="p">,</span> <span class="n">ascending</span><span class="o">=</span><span class="bp">False</span><span class="p">))</span>

    <span class="k">return</span> <span class="n">df</span>

<span class="k">def</span> <span class="nf">render_graph</span><span class="p">(</span><span class="n">G</span><span class="p">,</span> <span class="n">centrality_table</span><span class="p">,</span> <span class="n">show_labels</span><span class="o">=</span><span class="bp">True</span><span class="p">,</span> <span class="n">centrality_measure</span><span class="o">=</span><span class="s">'degree_centrality'</span><span class="p">):</span>
    <span class="n">output_file</span><span class="p">(</span><span class="sa">f</span><span class="s">"hand_</span><span class="si">{</span><span class="n">centrality_measure</span><span class="si">}</span><span class="s">.html"</span><span class="p">)</span>
    <span class="n">network_graph</span> <span class="o">=</span> <span class="n">from_networkx</span><span class="p">(</span>
        <span class="n">G</span><span class="p">,</span> <span class="n">nx</span><span class="p">.</span><span class="n">spring_layout</span><span class="p">,</span> <span class="n">scale</span><span class="o">=</span><span class="mi">20</span><span class="p">,</span> <span class="n">center</span><span class="o">=</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">),</span> <span class="n">weight</span><span class="o">=</span><span class="mi">1</span><span class="p">,</span> <span class="n">seed</span><span class="o">=</span><span class="mi">55</span><span class="p">)</span>

    <span class="k">try</span><span class="p">:</span>
        <span class="k">if</span> <span class="n">centrality_measure</span> <span class="o">==</span> <span class="s">'degree_centrality'</span><span class="p">:</span>

            <span class="n">adjusted_node_size</span> <span class="o">=</span> <span class="nb">dict</span><span class="p">([(</span><span class="n">node</span><span class="p">,</span> <span class="p">(</span><span class="n">degree</span><span class="p">))</span> <span class="k">for</span> <span class="n">node</span><span class="p">,</span> <span class="n">degree</span> <span class="ow">in</span> <span class="n">nx</span><span class="p">.</span><span class="n">degree</span><span class="p">(</span><span class="n">G</span><span class="p">)])</span>
        <span class="k">else</span><span class="p">:</span>

            <span class="c1"># LOG???
</span>            <span class="n">size</span> <span class="o">=</span> <span class="mi">80</span>
            <span class="n">adjusted_node_size</span> <span class="o">=</span> <span class="nb">dict</span><span class="p">([(</span><span class="n">node</span><span class="p">,</span> <span class="p">(</span><span class="n">value</span> <span class="o">*</span> <span class="n">size</span><span class="p">))</span> <span class="k">for</span> <span class="n">node</span><span class="p">,</span> <span class="n">value</span> <span class="ow">in</span> <span class="n">centrality_table</span><span class="p">[</span><span class="n">centrality_measure</span><span class="p">].</span><span class="n">to_dict</span><span class="p">().</span><span class="n">items</span><span class="p">()])</span>
    <span class="k">except</span> <span class="nb">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
        <span class="k">print</span><span class="p">(</span><span class="s">"Unable to calculate centralities"</span><span class="p">)</span>
        <span class="k">raise</span> <span class="n">e</span>


    <span class="n">HOVER_TOOLTIPS</span> <span class="o">=</span> <span class="p">[(</span><span class="s">"Node"</span><span class="p">,</span> <span class="s">"@index"</span><span class="p">)]</span>
    <span class="n">plot</span> <span class="o">=</span> <span class="n">figure</span><span class="p">(</span><span class="n">tooltips</span><span class="o">=</span><span class="n">HOVER_TOOLTIPS</span><span class="p">,</span>
                <span class="n">tools</span><span class="o">=</span><span class="s">"pan,wheel_zoom,save,reset"</span><span class="p">,</span>
                <span class="n">active_scroll</span><span class="o">=</span><span class="s">'wheel_zoom'</span><span class="p">,</span>
                <span class="n">title</span><span class="o">=</span><span class="s">'Network'</span><span class="p">,</span>
                <span class="n">width</span><span class="o">=</span><span class="mi">1000</span><span class="p">,</span>
                <span class="n">height</span><span class="o">=</span><span class="mi">700</span><span class="p">,</span>
                <span class="c1"># x_range=(-2, 2), y_range=(-2, 2),
</span>                <span class="n">background_fill_color</span><span class="o">=</span><span class="bp">None</span><span class="p">,</span>
                <span class="n">background_fill_alpha</span><span class="o">=</span><span class="mi">0</span><span class="p">,</span>
                <span class="n">border_fill_color</span><span class="o">=</span><span class="bp">None</span><span class="p">,</span>
                <span class="n">border_fill_alpha</span><span class="o">=</span><span class="mi">0</span><span class="p">,</span>
                <span class="n">outline_line_color</span><span class="o">=</span><span class="bp">None</span>
            <span class="p">)</span>
    <span class="n">plot</span><span class="p">.</span><span class="n">xgrid</span><span class="p">.</span><span class="n">grid_line_alpha</span> <span class="o">=</span> <span class="mi">0</span>
    <span class="n">plot</span><span class="p">.</span><span class="n">ygrid</span><span class="p">.</span><span class="n">grid_line_alpha</span> <span class="o">=</span> <span class="mi">0</span>
    <span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">adjusted_node_size</span><span class="p">)</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="p">:</span>

        <span class="n">nx</span><span class="p">.</span><span class="n">set_node_attributes</span><span class="p">(</span><span class="n">G</span><span class="p">,</span> <span class="n">name</span><span class="o">=</span><span class="s">'size'</span><span class="p">,</span> <span class="n">values</span><span class="o">=</span><span class="n">adjusted_node_size</span><span class="p">)</span>

        <span class="n">size_by_this_attribute</span> <span class="o">=</span> <span class="s">'adjusted_node_size'</span>

        <span class="n">source</span> <span class="o">=</span> <span class="n">ColumnDataSource</span><span class="p">(</span><span class="n">pd</span><span class="p">.</span><span class="n">DataFrame</span><span class="p">.</span><span class="n">from_dict</span><span class="p">(</span>
            <span class="p">{</span><span class="n">k</span><span class="p">:</span> <span class="n">v</span> <span class="k">for</span> <span class="n">k</span><span class="p">,</span> <span class="n">v</span> <span class="ow">in</span> <span class="n">G</span><span class="p">.</span><span class="n">nodes</span><span class="p">(</span><span class="n">data</span><span class="o">=</span><span class="bp">True</span><span class="p">)},</span> <span class="n">orient</span><span class="o">=</span><span class="s">'index'</span><span class="p">))</span>

        <span class="c1"># VARY SIZE VAR BASED ON DIFF MEASURES
</span>        <span class="n">network_graph</span><span class="p">.</span><span class="n">node_renderer</span><span class="p">.</span><span class="n">data_source</span> <span class="o">=</span> <span class="n">source</span>


        <span class="c1"># network_graph.node_renderer.glyph = Circle(radius='size', fill_color=linear_cmap('size', palettes.Spectral[8], min(adjusted_node_size.values()), max(adjusted_node_size.values())))
</span>        <span class="n">network_graph</span><span class="p">.</span><span class="n">node_renderer</span><span class="p">.</span><span class="n">glyph</span> <span class="o">=</span> <span class="n">Circle</span><span class="p">(</span><span class="n">radius</span><span class="o">=</span><span class="mf">0.3</span><span class="p">,</span> <span class="n">fill_color</span><span class="o">=</span><span class="n">linear_cmap</span><span class="p">(</span><span class="s">'size'</span><span class="p">,</span> <span class="n">palettes</span><span class="p">.</span><span class="n">Spectral</span><span class="p">[</span><span class="mi">8</span><span class="p">],</span> <span class="nb">min</span><span class="p">(</span><span class="n">adjusted_node_size</span><span class="p">.</span><span class="n">values</span><span class="p">()),</span> <span class="nb">max</span><span class="p">(</span><span class="n">adjusted_node_size</span><span class="p">.</span><span class="n">values</span><span class="p">())))</span>

        <span class="n">plot</span><span class="p">.</span><span class="n">renderers</span><span class="p">.</span><span class="n">append</span><span class="p">(</span><span class="n">network_graph</span><span class="p">)</span>

        <span class="k">if</span> <span class="n">show_labels</span> <span class="o">==</span> <span class="bp">True</span><span class="p">:</span>
            <span class="c1"># Add Labels
</span>            <span class="n">x</span><span class="p">,</span> <span class="n">y</span> <span class="o">=</span> <span class="nb">zip</span><span class="p">(</span><span class="o">*</span><span class="n">network_graph</span><span class="p">.</span><span class="n">layout_provider</span><span class="p">.</span><span class="n">graph_layout</span><span class="p">.</span><span class="n">values</span><span class="p">())</span>
            <span class="n">node_labels</span> <span class="o">=</span> <span class="nb">list</span><span class="p">(</span><span class="n">G</span><span class="p">.</span><span class="n">nodes</span><span class="p">())</span>
            <span class="n">source</span> <span class="o">=</span> <span class="n">ColumnDataSource</span><span class="p">(</span>
                <span class="p">{</span><span class="s">'x'</span><span class="p">:</span> <span class="n">x</span><span class="p">,</span> <span class="s">'y'</span><span class="p">:</span> <span class="n">y</span><span class="p">,</span> <span class="s">'cn'</span><span class="p">:</span> <span class="p">[</span><span class="n">node_labels</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="nb">len</span><span class="p">(</span><span class="n">x</span><span class="p">))]})</span>
            <span class="n">labels</span> <span class="o">=</span> <span class="n">LabelSet</span><span class="p">(</span><span class="n">x</span><span class="o">=</span><span class="s">'x'</span><span class="p">,</span> <span class="n">y</span><span class="o">=</span><span class="s">'y'</span><span class="p">,</span> <span class="n">text</span><span class="o">=</span><span class="s">'cn'</span><span class="p">,</span> <span class="n">source</span><span class="o">=</span><span class="n">source</span><span class="p">,</span> <span class="n">text_font_size</span><span class="o">=</span><span class="s">'12px'</span><span class="p">)</span>
            <span class="n">plot</span><span class="p">.</span><span class="n">renderers</span><span class="p">.</span><span class="n">append</span><span class="p">(</span><span class="n">labels</span><span class="p">)</span>
            <span class="n">callback</span> <span class="o">=</span> <span class="n">CustomJS</span><span class="p">(</span><span class="n">args</span><span class="o">=</span><span class="nb">dict</span><span class="p">(</span><span class="n">labels</span><span class="o">=</span><span class="n">labels</span><span class="p">,</span> <span class="n">x_range</span><span class="o">=</span><span class="n">plot</span><span class="p">.</span><span class="n">x_range</span><span class="p">),</span> <span class="n">code</span><span class="o">=</span><span class="s">"""
                const span = x_range.end - x_range.start
                const base = 11
                const scaled = Math.min(30, Math.max(6, base / span * 11))
                labels.text_font_size = scaled + 'px'
            """</span><span class="p">)</span>

            <span class="n">plot</span><span class="p">.</span><span class="n">x_range</span><span class="p">.</span><span class="n">js_on_change</span><span class="p">(</span><span class="s">'start'</span><span class="p">,</span> <span class="n">callback</span><span class="p">)</span>
            <span class="n">plot</span><span class="p">.</span><span class="n">x_range</span><span class="p">.</span><span class="n">js_on_change</span><span class="p">(</span><span class="s">'end'</span><span class="p">,</span> <span class="n">callback</span><span class="p">)</span>

    <span class="k">return</span> <span class="n">plot</span>

<span class="n">data_dict</span> <span class="o">=</span> <span class="p">{}</span>

<span class="n">story_data</span> <span class="o">=</span> <span class="p">{</span>
    <span class="s">"HAND"</span><span class="p">:</span> <span class="p">[</span><span class="s">"OS 633/75"</span><span class="p">,</span> <span class="s">"OS 385/14"</span><span class="p">,</span> <span class="s">"OS 385/14"</span><span class="p">,</span> <span class="s">"OS 385/14"</span><span class="p">,</span> <span class="s">"OS 449/13"</span><span class="p">,</span> <span class="s">"OS 385/14"</span><span class="p">,</span> <span class="s">"OS 590/7"</span><span class="p">,</span> <span class="s">"OS 385/14"</span><span class="p">,</span> <span class="s">"OS 449/13"</span><span class="p">],</span>
    <span class="s">"NUGAN"</span><span class="p">:</span> <span class="p">[</span><span class="s">"OS 109/72"</span><span class="p">,</span> <span class="s">"OS 633/75"</span><span class="p">,</span> <span class="p">],</span>
    <span class="s">"WILSON"</span><span class="p">:</span> <span class="p">[</span><span class="s">"OS 161/15"</span><span class="p">,</span> <span class="s">"OS 615/53"</span><span class="p">,</span> <span class="s">"OS 449/13"</span><span class="p">,</span> <span class="s">"OS 161/15"</span><span class="p">,</span> <span class="s">"OS 786/95"</span><span class="p">],</span>
    <span class="s">"HOUGHTON"</span><span class="p">:</span> <span class="p">[</span><span class="s">"OS 633/75"</span><span class="p">,</span> <span class="s">"OS 802/84"</span><span class="p">,</span> <span class="s">"OS 161/15"</span><span class="p">,</span> <span class="s">"OS 802/84"</span><span class="p">],</span>
    <span class="s">"COLBY"</span><span class="p">:</span> <span class="p">[</span><span class="s">"OS 488/17"</span><span class="p">],</span>
    <span class="s">"HELLIWELL"</span><span class="p">:</span> <span class="p">[</span><span class="s">"OS 267/4"</span><span class="p">,</span> <span class="s">"OS 536/84"</span><span class="p">],</span>
<span class="p">}</span>

<span class="k">for</span> <span class="n">name</span><span class="p">,</span><span class="n">numbers</span> <span class="ow">in</span> <span class="n">story_data</span><span class="p">.</span><span class="n">items</span><span class="p">():</span>
    <span class="n">data_dict</span><span class="p">[</span><span class="n">name</span><span class="p">]</span> <span class="o">=</span> <span class="nb">set</span><span class="p">(</span><span class="n">numbers</span><span class="p">)</span>

<span class="n">non_story_data</span> <span class="o">=</span> <span class="n">json</span><span class="p">.</span><span class="n">load</span><span class="p">(</span><span class="nb">open</span><span class="p">(</span><span class="s">'hand.json'</span><span class="p">,</span> <span class="s">'r'</span><span class="p">))</span>

<span class="k">for</span> <span class="n">d</span> <span class="ow">in</span> <span class="n">non_story_data</span><span class="p">:</span>
    <span class="k">if</span> <span class="n">d</span><span class="p">[</span><span class="s">'surname'</span><span class="p">]</span> <span class="ow">in</span> <span class="n">data_dict</span><span class="p">:</span>
        <span class="n">data_dict</span><span class="p">[</span><span class="n">d</span><span class="p">[</span><span class="s">'surname'</span><span class="p">]].</span><span class="n">update</span><span class="p">([</span><span class="n">d</span><span class="p">[</span><span class="s">'refs'</span><span class="p">]])</span>
    <span class="k">else</span><span class="p">:</span>
        <span class="n">data_dict</span><span class="p">[</span><span class="n">d</span><span class="p">[</span><span class="s">'surname'</span><span class="p">]]</span> <span class="o">=</span> <span class="nb">set</span><span class="p">(</span><span class="n">d</span><span class="p">[</span><span class="s">'refs'</span><span class="p">])</span>

<span class="c1"># data_dict now contains all OS refs
</span><span class="k">print</span><span class="p">(</span><span class="n">data_dict</span><span class="p">)</span>

<span class="n">inverse_data_dict</span> <span class="o">=</span> <span class="p">{}</span>
<span class="k">for</span> <span class="n">k</span><span class="p">,</span><span class="n">v</span> <span class="ow">in</span> <span class="n">data_dict</span><span class="p">.</span><span class="n">items</span><span class="p">():</span>
    <span class="k">for</span> <span class="n">x</span> <span class="ow">in</span> <span class="n">v</span><span class="p">:</span>
        <span class="n">inverse_data_dict</span><span class="p">.</span><span class="n">setdefault</span><span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="p">[]).</span><span class="n">append</span><span class="p">(</span><span class="n">k</span><span class="p">)</span>

<span class="n">G</span> <span class="o">=</span> <span class="n">nx</span><span class="p">.</span><span class="n">Graph</span><span class="p">()</span>
<span class="k">for</span> <span class="n">name</span><span class="p">,</span><span class="n">numbers</span> <span class="ow">in</span> <span class="n">data_dict</span><span class="p">.</span><span class="n">items</span><span class="p">():</span>
    <span class="n">G</span><span class="p">.</span><span class="n">add_node</span><span class="p">(</span><span class="n">name</span><span class="p">)</span>
    <span class="k">for</span> <span class="n">n</span> <span class="ow">in</span> <span class="n">numbers</span><span class="p">:</span>
        <span class="k">for</span> <span class="n">oname</span> <span class="ow">in</span> <span class="n">inverse_data_dict</span><span class="p">[</span><span class="n">n</span><span class="p">]:</span>
            <span class="k">if</span> <span class="n">oname</span> <span class="o">!=</span> <span class="n">name</span><span class="p">:</span>
                <span class="n">G</span><span class="p">.</span><span class="n">add_edge</span><span class="p">(</span><span class="n">name</span><span class="p">,</span> <span class="n">oname</span><span class="p">)</span>

<span class="c1"># G = nx.fast_gnp_random_graph(1000, 0.01)
</span>
<span class="n">df</span> <span class="o">=</span> <span class="n">build_centrality_table</span><span class="p">(</span><span class="n">G</span><span class="p">)</span>
<span class="n">eig_plt</span> <span class="o">=</span> <span class="n">render_graph</span><span class="p">(</span><span class="n">G</span><span class="p">,</span> <span class="n">df</span><span class="p">,</span> <span class="n">show_labels</span><span class="o">=</span><span class="bp">True</span><span class="p">,</span> <span class="n">centrality_measure</span><span class="o">=</span><span class="s">'eigenvector_centrality'</span><span class="p">)</span>

<span class="n">show</span><span class="p">(</span><span class="n">eig_plt</span><span class="p">)</span>

<span class="n">deg_plt</span> <span class="o">=</span> <span class="n">render_graph</span><span class="p">(</span><span class="n">G</span><span class="p">,</span> <span class="n">df</span><span class="p">,</span> <span class="n">show_labels</span><span class="o">=</span><span class="bp">True</span><span class="p">,</span> <span class="n">centrality_measure</span><span class="o">=</span><span class="s">'degree_centrality'</span><span class="p">)</span>
<span class="n">show</span><span class="p">(</span><span class="n">deg_plt</span><span class="p">)</span>
</code></pre></div>
  </div>
</div>

<p>If we run this, with no node colourings, we see the shape of the relationships:
IMG UNCOLOURED GRAPH</p>

<p>This tells us some interesting things:</p>

<ul>
<li>There is a single large, well connected cluster </li>
<li>There are multiple nodes with 0 or 1 edges, dotted around the edges</li>
<li>There are a small number of disconnected minor clusters of 4</li>
</ul>

<p>If we were to apply our theories of Social Network Analysis here we&#39;d say the data shows a single group of well connected associates,with lotsof  strange isolated people that don&#39;t fit in. This is typically an unusual graph if it were to showreal people / relationships, as we&#39;d typically expect more inerconnectedness, and fewer isolated groups.</p>

<p>Now let&#39;s refine our analysis, by calculating centrality scores using DEGREE CENTRALITY, and using this to colour the graph. This should tell us the relative &quot;importance&quot; of certain nodes.<label for='qI6ke' class='margin-toggle sidenote-number'></label><input type='checkbox' id='qI6ke' class='margin-toggle'/><span class='sidenote'>Red indicates high centralities, blue low according to the Bokeh &quot;Spectral&quot; palette: <a href="https://docs.bokeh.org/en/latest/docs/reference/palettes.html">https://docs.bokeh.org/en/latest/docs/reference/palettes.html</a> </span></p>

<iframe src="/assets/img/hand/hand_degree_centrality.html" width="100%" height="700" style="border:none; background:transparent;" frameborder="0"></iframe>

<p>Now we can see things more clearly, we can see there are a small number of nodes with a high degree centrality (red). We can also see that on the edges of the large cluster are distinct small clusters.</p>

<p>As we can see, this is a kind of confusing analysis: there are no clear winners with high centrality, instead lots of nodes appear to have high centrality. This is what makes degree centrality a less useful metric, especially in Social Network Analysis. What if we calculate eigenvector centrality instead, not degree centrality?</p>

<!-- ![](/assets/img/hand/eigenvector_graph.png) -->

<iframe src="/assets/img/hand/hand_eigenvector_centrality.html" width="100%" height="700" style="border:none; background:transparent;" frameborder="0"></iframe>

<p>As discussed earlier, eigenvector centrality tends to pick out only the most connected nodes, and we can see this here, as one concentrated &quot;centre&quot; of the graph, coloured with red.</p>

<h2 id="analysing-results">Analysing results</h2>

<p>So originally I theorised that we could &quot;solve&quot; the game, by applying Social Network Analysis, and that by calculating centralities of all the characters in the game, we&#39;d be able to find the most influential characters, and solve the mystery.</p>

<p>So did this work?</p>

<p>If the hypothesis was correct, then perhaps the game&#39;s main characters are the most well connected / influential people in our archives. If we print out the table, sorted by eigenvector centralities, we&#39;d expect to find the &quot;most influential&quot; characters, and here are the results:</p>
<div class="highlight"><pre><code class="language-" data-lang="">name              closeness_centrality  degree_centrality  eigenvector_centrality  katz_centrality
PAYNE             0.154276           0.028662            4.239510e-01           0.112430
PARK              0.144626           0.025478            3.768897e-01           0.101089
GEYER             0.146413           0.022293            3.678812e-01           0.098157
FULLER            0.148361           0.019108            3.514795e-01           0.092221
OWEN              0.141285           0.019108            3.501144e-01           0.091208
...                  ...                ...                   ...                  ...
NORMAN            0.000000           0.000000            3.576366e-59           0.036707
NYLUND            0.000000           0.000000            3.576366e-59           0.036707
OGILVIE           0.000000           0.000000            3.576366e-59           0.036707
OLIVER            0.000000           0.000000            3.576366e-59           0.036707
XIAO              0.000000           0.000000            3.576366e-59           0.036707

</code></pre></div>
<p>Okay... these aren&#39;t the game&#39;s main characters, so not the result we might have suspected. Turns out the data doesn&#39;t match our hypothesis. </p>

<p>Where are the main characters in our graph? If we look closely we can see they are concentrated in a small cluster by themselves. They&#39;re not connected to other parts of the graph, and as a result, not very &quot;central&quot;. They don&#39;t register at all on our high-centrality list:</p>

<p><img src="/assets/img/hand/cluster.png" alt="">
So what does this mean? It means that our characters are differentiated not by their connections, but by their lack of them. It seems an illogical conclusion: we&#39;d assume that these individuals, who ran an international bank, smuggled arms and guns across multiple continents would be extremely &quot;well connected&quot;. Why isn&#39;t this shown in our analysis?</p>

<p>In a sense the graphing has worked: we&#39;ve identified the cluster of 4 people closely associated with Nugan, which was sort of our original objective. <label for='FssrJ' class='margin-toggle sidenote-number'></label><input type='checkbox' id='FssrJ' class='margin-toggle'/><span class='sidenote'>Except, apparently, this does not include one of the central characters: Collins. Not sure why but I think in the game your supposed to make a leap of faith to work out how hes connected, and he doesn&#39;t appear related in any files directly. </span>. Additionally, we can&#39;t say that this grouping is really special, as there are multiple other standalone cliques:</p>

<ul>
<li>The MONROE-IGUINA-VOSS-LYNCH-TYSON-ESTES-JI cluster</li>
<li>The KNOX-ELLIOT-IRWIN-KAUFMAN cluster</li>
</ul>

<p>The reason for this unexpected result, is that this isn&#39;t a real dataset. </p>

<!-- Just from looking at the nodes that aren't in the main cluster, you can kind of tell that these aren't real people/files: their data looks almost uniformly random, not like real people. Real people have clusters, cliques, little pockets of centrality of their own, but here there's only 1 cluster really, and all those weird subgroups round the outside. -->

<p>In fact, if we graph a histogram of the degree centralities, what do we see:</p>

<div class="dropdown-code">
  <button class="dropdown-code-toggle" onclick="toggleDropdownCode('dropdown-45662')" aria-expanded="false">
    <span class="dropdown-arrow">▶</span>
    <span class="dropdown-title">histogram.py</span>
  </button>
  <div id="dropdown-45662" class="dropdown-code-content" hidden>
    <div class="highlight"><pre><code class="language-python" data-lang="python"><span class="k">def</span> <span class="nf">plot_histogram</span><span class="p">(</span><span class="n">df</span><span class="p">,</span> <span class="n">column</span><span class="o">=</span><span class="s">'connections'</span><span class="p">,</span> <span class="n">bins</span><span class="o">=</span><span class="mi">20</span><span class="p">,</span> <span class="n">output</span><span class="o">=</span><span class="s">'histogram.html'</span><span class="p">):</span>
    <span class="n">data</span> <span class="o">=</span> <span class="n">df</span><span class="p">[</span><span class="n">column</span><span class="p">].</span><span class="n">dropna</span><span class="p">()</span>

    <span class="c1"># Fixed range 0-1 for centrality metrics
</span>    <span class="n">hist</span><span class="p">,</span> <span class="n">edges</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="n">histogram</span><span class="p">(</span><span class="n">data</span><span class="p">,</span> <span class="n">bins</span><span class="o">=</span><span class="n">bins</span><span class="p">)</span>

    <span class="n">output_file</span><span class="p">(</span><span class="n">output</span><span class="p">)</span>

    <span class="n">plot</span> <span class="o">=</span> <span class="n">figure</span><span class="p">(</span><span class="n">title</span><span class="o">=</span><span class="sa">f</span><span class="s">'Distribution of </span><span class="si">{</span><span class="n">column</span><span class="si">}</span><span class="s">'</span><span class="p">,</span>
                  <span class="n">x_axis_label</span><span class="o">=</span><span class="n">column</span><span class="p">,</span>
                  <span class="n">y_axis_label</span><span class="o">=</span><span class="s">'Frequency'</span><span class="p">,</span>
                  <span class="n">x_range</span><span class="o">=</span><span class="p">(</span><span class="o">-</span><span class="mi">2</span><span class="p">,</span> <span class="mi">10</span><span class="p">),</span>
                  <span class="n">width</span><span class="o">=</span><span class="mi">800</span><span class="p">,</span> <span class="n">height</span><span class="o">=</span><span class="mi">400</span><span class="p">,</span>
                  <span class="n">tools</span><span class="o">=</span><span class="s">'pan,wheel_zoom,reset,save'</span><span class="p">,</span>
                  <span class="n">background_fill_color</span><span class="o">=</span><span class="bp">None</span><span class="p">,</span>
                    <span class="n">background_fill_alpha</span><span class="o">=</span><span class="mi">0</span><span class="p">,</span>
                    <span class="n">border_fill_color</span><span class="o">=</span><span class="bp">None</span><span class="p">,</span>
                    <span class="n">border_fill_alpha</span><span class="o">=</span><span class="mi">0</span><span class="p">,</span>
                    <span class="n">outline_line_color</span><span class="o">=</span><span class="bp">None</span><span class="p">)</span>

    <span class="n">plot</span><span class="p">.</span><span class="n">quad</span><span class="p">(</span><span class="n">top</span><span class="o">=</span><span class="n">hist</span><span class="p">,</span> <span class="n">bottom</span><span class="o">=</span><span class="mi">0</span><span class="p">,</span> <span class="n">left</span><span class="o">=</span><span class="n">edges</span><span class="p">[:</span><span class="o">-</span><span class="mi">1</span><span class="p">],</span> <span class="n">right</span><span class="o">=</span><span class="n">edges</span><span class="p">[</span><span class="mi">1</span><span class="p">:],</span>
              <span class="n">fill_color</span><span class="o">=</span><span class="s">'steelblue'</span><span class="p">,</span> <span class="n">line_color</span><span class="o">=</span><span class="s">'black'</span><span class="p">,</span> <span class="n">alpha</span><span class="o">=</span><span class="mf">0.7</span><span class="p">)</span>

    <span class="n">show</span><span class="p">(</span><span class="n">plot</span><span class="p">)</span>

<span class="n">df</span> <span class="o">=</span> <span class="n">pd</span><span class="p">.</span><span class="n">DataFrame</span><span class="p">({</span>
    <span class="s">'node'</span><span class="p">:</span> <span class="nb">list</span><span class="p">(</span><span class="n">G</span><span class="p">.</span><span class="n">nodes</span><span class="p">()),</span>
    <span class="s">'connections'</span><span class="p">:</span> <span class="p">[</span><span class="n">G</span><span class="p">.</span><span class="n">degree</span><span class="p">(</span><span class="n">n</span><span class="p">)</span> <span class="k">for</span> <span class="n">n</span> <span class="ow">in</span> <span class="n">G</span><span class="p">.</span><span class="n">nodes</span><span class="p">()]</span>
<span class="p">})</span>

<span class="n">plot_histogram</span><span class="p">(</span><span class="n">df</span><span class="p">,</span> <span class="n">column</span><span class="o">=</span><span class="s">'connections'</span><span class="p">,</span> <span class="n">bins</span><span class="o">=</span><span class="mi">30</span><span class="p">)</span>
</code></pre></div>
  </div>
</div>

<iframe src="/assets/img/hand/histogram.html" width="100%" height="400" style="border:none; background:transparent;" frameborder="0"></iframe>

<p>But actually this has an interesting real world implication on the biases of social network analysis and other algorithms: data bias in the input will result in bias in the output. In the world of the game, we assume that this archive is a set of data collected independently and without bias. But let&#39;s say the collection of newspaper clippings in the game was collected by someone who was actually trying to investigate an unrelated case, would that be unbiased?</p>

<p>it wouldn&#39;t contain much Nugan-Hand material, or wouldn&#39;t link them to other events.</p>

<p>On the other hand, what if the people responsible for gathering data already knew of Nugan Hand&#39;s connection to the CIA and were obsessed with proving it? wouldn&#39;t they have disproportinality picked out clippings that support that hypothesis? The resulting data would potentially assign higher centralities to the main cast: the only conclusion that could be drawn from this data is that Nugan hand was connected to the CIA. This is an example of the confirmation/narrative biases that draw people into conspiracy theories every day: you look for a pattern in data, and lo and behold, you find one.</p>

<p>If we&#39;d gathered  a completely random set of newspaper clippings, or rather, all the newspaper articles in the world, maybe we&#39;d be able to see other conclusions. This illustrates the real dangers of biased data, and in drawing real world conclusions from social network analysis: the algorithm can only analyse what it knows about, and you have to be very careful to avoid bias in datasets.</p>

<h2 id="summary">Summary</h2>

<p>So while ultimately my hypothesis was wrong, and the data wasn&#39;t representative of reality , I think this ended up an interesting exploration in network analysis and it&#39;s applications.</p>]]></content><author><name>Hendo</name></author><category term="games" /><summary type="html"><![CDATA[Lots of fiction, in all forms, focuses on the world of spying and espionage. There are thousands of books, films, and games that cover the subject, and from different angles. There are non-fiction books and documentaries that study the true history of the subject, romantic fiction, and all sorts in-between.]]></summary></entry><entry><title type="html">Real and Imagined Prague</title><link href="/prague/" rel="alternate" type="text/html" title="Real and Imagined Prague" /><published>2024-05-20T00:00:00-05:00</published><updated>2024-05-20T00:00:00-05:00</updated><id>/prague</id><content type="html" xml:base="/prague/"><![CDATA[<p>Prague is the setting for Deus Ex&#39;s fourth adventure: <a href="https://store.steampowered.com/app/337000/Deus_Ex_Mankind_Divided/">Deus Ex: Mankind Divided</a>, set in 2029.</p>

<p>It&#39;s a beautiful city, and the game imagines an elegant and at times brutalist future for it, blending the old town with new structures and ideas. Glass, metal, concrete, and police in mech-suits crawl over the old gothic buildings, in a style the artists dubbed &quot;techno-feudalism&quot;. This post is just a lighthearted comparison between the game and real life, seeing how they match up and if there are any interesting nods to the real city hidden within the game. At the end I&#39;ve linked an interview with some of the game&#39;s designers that gives some insight into how they built the city.</p>

<p><img src="/assets/img/deus_ex/Prague_Concept_mini_artbook1.webp" alt=""></p>

<style> 
.row {
  display: flex;
  width: 70pc;
}

.column {
  flex: 50%;
  padding-top: 20px;
  padding-right: 20px;
}
</style>

<h2 id="past">Past</h2>

<p>Prague has evolved and radiated out from the seat of power: <a href="https://en.wikipedia.org/wiki/Prague_Castle">Pražský hrad</a>, on the hill west of the river. The city boomed due to it&#39;s central position in Europe, situated on a river for trade, and with defensible terrain. In the 14th century Prague became the seat of the Holy Roman Empire, during which the city prospered and grew, it&#39;s this period which created many of the elaborate gothic landmarks Prague is famous for today.</p>

<!-- ![](/assets/img/deus_ex/Praha_Hrad_1607.jpg) -->

<p><img src="/assets/img/deus_ex/old.png" alt="">
<p style="text-align:center;"><em>In this picture from 1607, much of the distincitive skyline remains visible today</em></p></p>

<h2 id="2029">2029</h2>

<p>The slices of prague seen in Deus Ex are only a small fraction of the city. Specifically, the map is within <em>Staré Město</em>: the old town on the east side of the river<label for='Bwyan' class='margin-toggle sidenote-number'></label><input type='checkbox' id='Bwyan' class='margin-toggle'/><span class='sidenote'>Other maps in the game, as well as the in-game compass, change the orientation to make west north and east become south </span>. Some maps in-game are nice enough to overlay the map on a real map, placing the map just between Mánes Bridge and Charles Bridge. Convinient, as that&#39;s where all the landmarks are. As we can see, the street layouts don&#39;t match, and the game world isn&#39;t an attempt to accurately model the real Prague. The central train line that bisects the map doesn&#39;t exist in real life, but otherwise the game map is somewhat true to this placement, with the in-game architecture roughly correct for this area of the city, and it makes sense for the landmarks in the game such as Palisade Bank to be situated in this central part of the city.</p>

<!-- ![](/assets/img/deus_ex/Prague_map_(tourist_center).webp) -->

<p><img src="/assets/img/deus_ex/map_labelled.png" alt="">
<p style="text-align:center;"><em>A map found within a disused tourist office in-game</em></p></p>

<p>Here are the real streets that area relates to, courtesy of Google earth:
<img src="/assets/img/deus_ex/prague_old_town-label.png" alt=""></p>

<p>The buildings are pretty much as they are now, and as they existed in the 20th century, but with some extra sculptures.
There&#39;s now a metro though, and some ridiculously large, impossibly cantilevered futuristic structures loom overhead.
<label for='iuuxE' class='margin-toggle'> &#8853;</label><input type='checkbox' id='iuuxE' class='margin-toggle'/><span class='marginnote'><br><br><br><br><br><br><br><br><br><br><br><br>The spires on the hill are a fairly accurate rendition of Pražský hrad. The silhouette is the same as the old drawing further up, although flipped </span></p>

<p><img src="/assets/img/deus_ex/blades.webp" alt=""></p>

<h2 id="rooftops-and-the-mysterious-case-of-the-towers">Rooftops and the mysterious case of the towers</h2>

<p>The town, especially the old town, is quite dense in the medieval European style, and as you play an elite cyborg ninja sniper with a recently discovered knack for teleportation, there&#39;s quite a few opportunities to admire the skybox and look out over the roofs of Prague. In fact this is one of the main ways you get a sense of the city&#39;s geography while playing, as otherwise you&#39;re walking through narrow alleys, tunnels, and vents.</p>

<p>One of Prague&#39;s distinguishing features are the gothic spires attached to the bridges, clock towers, and old town hall, so much that is has been known as the city of 100 spires:</p>

<p><img src="/assets/img/deus_ex/spires1.jpg" alt=""></p>

<p>A nice touch in Mankind Divided is that you can occasionally glimpse these poking out of the skyline. </p>

<p><img src="/assets/img/deus_ex/dex10.jpg" alt=""></p>

<p>When I noticed this I tried to cross reference them, to see if the approximate in game positions made sense. I came to the conclusion they didn&#39;t match up, as in the game you can find more towers than there are in real life, and the relative directions don&#39;t quite make sense:</p>

<p><img src="/assets/img/deus_ex/spires2.png" alt=""></p>

<h2 id="the-clocktower">The Clocktower</h2>

<p>One of the mentioned towers is Prague&#39;s astronomical clocktower, located in the old town by the open square and town hall. You can in fact find the clocktower in the game, recognisable by it&#39;s distinctive shape. In 2029, the bottom of the tower is clad in brutalist concrete sculpture, obscuring the famous clock at its base. You can find a clip of the in-game clocktower during the daytime here, showing off the concrete structure: <a href="https://youtu.be/eMFL7ettk7Y?t=42">https://youtu.be/eMFL7ettk7Y?t=42</a>.
<label for='asd2' class='margin-toggle'>&#8853;</label><input type='checkbox' id='asd2' class='margin-toggle'/><span class='marginnote'><img class='fullwidth' src='/assets/img/deus_ex/clock.jpg'/><br><a href="https://en.wikipedia.org/wiki/Prague_astronomical_clock">Prague Astronomical Clock</a>. The lower part was painted by Joseph Mánes, who gives his name to one of the bridges discussed earlier</span></p>

<div class="row">
&nbsp;&nbsp;
<img src="/assets/img/deus_ex/clock_real.png" style="width: 30pc">
<span style="display:inline-block;width:50px;"></span>
<img src="/assets/img/deus_ex/clock_game.png" style="width: 25pc">
</div>

<h2 id="bridges-and-castles">Bridges and castles</h2>

<p>Prague is famous for it&#39;s bridges, especially <a href="https://en.wikipedia.org/wiki/Charles_Bridge">Charles Bridge</a>, which look beautiful at night, and criss-cross the Vltava. Sadly due to the small map, there isn&#39;t an opportunity to walk across one, but there are a couple of neat viewpoints. In real life, looking west across the river from old town shows a view of Prasky Hrad. But in the game they&#39;ve flipped the view, confusingly if you look west, the view you see is that of the east side. This is shown in the 2 images below, with the same distinctive rooftops highlighted in each:</p>

<p><img src="/assets/img/deus_ex/river_game.png" alt="">
<p style="text-align:center;"><em>Across the Vltava in the game, remembering the game&#39;s map places us on the east side of the river</em></p></p>

<p><img src="/assets/img/deus_ex/river_real.png" alt="">
<p style="text-align:center;"><em>The same view in reality</em></p></p>

<p>The real view west from the game&#39;s position on the east would show the castle on the hill to the north west:
<br>
<img src="/assets/img/deus_ex/bridge-real.jpg" alt=""></p>

<h2 id="the-theatre">The Theatre</h2>

<p>The exterior of the &quot;Dvali&quot; theatre, the setting of the <a href="https://deusex.fandom.com/wiki/Hunting_Down_the_Final_Clues">&quot;Hunting Down the Final Clues&quot; mission</a>, shows off a domed building on a corner, with a canopy and tympanum<label for='5j6TD' class='margin-toggle sidenote-number'></label><input type='checkbox' id='5j6TD' class='margin-toggle'/><span class='sidenote'> </span><label for='asd' class='margin-toggle'>&#8853;</label><input type='checkbox' id='asd' class='margin-toggle'/><span class='marginnote'><img class='fullwidth' src='/assets/img/deus_ex/typanum.png'/><br>For more &#39;What style is this building and what do I call this funny bit&#39; information, check out <a href="https://www.waterstones.com/book/rices-architectural-primer/matthew-rice/9780747597483">Rice&#39;s Architectural Primer</a></span>. It&#39;s a little hard to make out in the image below, due to the large mech and pouring rain. <em>R.U.R</em> on the billboard is a reference to <a href="https://en.wikipedia.org/wiki/R.U.R.">Rossum&#39;s Universal Robots</a>, a Czech play which first brought about the term &quot;robot&quot;, and discussed the ideas about the rights of androids, a central theme of the recent Deus Ex entries.</p>

<p><label for='asd2' class='margin-toggle'>&#8853;</label><input type='checkbox' id='asd2' class='margin-toggle'/><span class='marginnote'><img class='fullwidth' src='/assets/img/deus_ex/Dvali_theatre.webp'/><br></span></p>

<p><img src="/assets/img/deus_ex/theatre-outside-game.jpg" alt="">
<p style="text-align:center;"><em>Exterior of un-named theatre in Deus Ex: Mankind Divided</em></p></p>

<p>This actually appears to be a scaled down model of the <a href="https://architectureofcities.com/prague#art">Art Nouveau</a> Prague Municipal House, not really a theatre. You can see the same corner shape, the distinctive dome, narrow arched windows, as well as the canopy and tympanum.</p>

<p><img src="/assets/img/deus_ex/municipal-house-real.jpg" alt=""></p>

<blockquote>
<p>Exterior of Prague municipal house in real life, inspiration for the in-game theatre</p>
</blockquote>

<!-- However it appears the interior is more closely modelled on Vinohrady Theatre, even the baroque stylings of the balconies:


<div class="row">
    <div class="column">
        <img src="/assets/img/deus_ex/theatre_inside_game2.jpg" style="width: 35pc">
    </div>
    <div class="column">
        <img src="/assets/img/deus_ex/theatre_inside_real.jpg" style="width: 35pc">
    </div>
</div>

    1: Game map of theatre
    2: Real layout of Vinohrady Theater

 -->

<!-- <div class="row">
    <div class="column">
        <img src="/assets/img/deus_ex/inside-theatre-game.jpg" style="height: 25pc; width: auto">
    </div>
    <div class="column">
        <img src="/assets/img/deus_ex/inside-theatre.jpg" style="width: 35pc">
    </div>
</div>
 -->

<h2 id="the-time-machine">The Time Machine</h2>

<p>&quot;The Time Machine&quot; is a bookshop encountered early during the Deus Ex story, with no single inspiration I could find. The closest reference I could find was that the quirky book-arched front door is reminisicent of the window of <a href="https://maps.app.goo.gl/UmJ4iVo3U6G3KypD6">This shop</a>, shown below. Prague&#39;s famous library, <a href="https://www.strahovskyklaster.cz/en/strahov-library">Strahov Monastery Library</a> doesn&#39;t seem to be referenced, and is geographically outside of the game&#39;s area, situated west of the river close to the castle.</p>

<div class="row">
    <div class="column">
        <img src="/assets/img/deus_ex/bookshop-game.jpeg" style="height: 25pc; width: auto">
    </div>
    <div class="column">
        <img src="/assets/img/deus_ex/bookshop-real.jpeg" style="width: 35pc">
    </div>
</div>

<p><span class='newthought'>The naming of The Time Machine</span>  is another neat nod to the game&#39;s themes and classic science fiction. <a href="https://en.wikipedia.org/wiki/The_Time_Machine">HG Well&#39;s &quot;The Time Machine&quot;</a> is a seminal sci-fi work, also looking forward and envisioning a more divided future, with vast inequality between rich and poor, in which the working classes have lost their humanity.</p>

<p>There the parallels end, since H.G Wells&#39; novel ends with the protagonist travelling forwards in time to when the world is filled with giant crabs and the sun becomes a red giant, and in the game you fly to London to fight the Illuminati, both equally plausible and compelling visions.<label for='2g7tm' class='margin-toggle'> &#8853;</label><input type='checkbox' id='2g7tm' class='margin-toggle'/><span class='marginnote'>Another link to classic sci-fi can be found nearby: a Jewellers called &quot;Vern&#39;s Jewels&quot; </span><label for='id1' class='margin-toggle'>&#8853;</label><input type='checkbox' id='id1' class='margin-toggle'/><span class='marginnote'><img class='fullwidth' src='/assets/img/deus_ex/vernsjewels.png'/><br></span></p>

<h2 id="wrapping-up">Wrapping up</h2>

<p>I hope that&#39;s been an interesting dive into the city, with a bit of architecture and sci-fi history sprinkled in. If you&#39;ve not played it I can heartily recommend playing the game, and if you want to read more, the following links provide some insights from the game&#39;s level designers:</p>

<ul>
<li><a href="https://80.lv/articles/deus-ex-mankind-divided-building-prague-city-hub/">https://80.lv/articles/deus-ex-mankind-divided-building-prague-city-hub/</a></li>
<li><a href="https://deusex.fandom.com/wiki/Prague">https://deusex.fandom.com/wiki/Prague</a></li>
<li><a href="https://onemorecontinue.com/deus-ex-mankind-divided-prague-city/">https://onemorecontinue.com/deus-ex-mankind-divided-prague-city/</a></li>
</ul>]]></content><author><name>Hendo</name></author><category term="cities" /><summary type="html"><![CDATA[Prague is the setting for Deus Ex&#39;s fourth adventure: Deus Ex: Mankind Divided, set in 2029.]]></summary></entry><entry><title type="html">Blog design</title><link href="/design/" rel="alternate" type="text/html" title="Blog design" /><published>2024-05-20T00:00:00-05:00</published><updated>2024-05-20T00:00:00-05:00</updated><id>/design</id><content type="html" xml:base="/design/"><![CDATA[<h3 id="design">Design</h3>

<p>The squarish, block based design is supposed to imitate retro-futuristic computer interfaces such as <a href="https://www.gameuidatabase.com/gameData.php?id=98">Metal Gear Solid (V)</a>, <a href="https://store.steampowered.com/app/13570/Tom_Clancys_Splinter_Cell_Chaos_Theory/">Chaos Theory</a> or <a href="https://www.platinumgames.com/official-blog/article/9624">Nier Automata</a>:</p>

<p><img src="/assets/img/mgsv.jpg" alt=""></p>

<blockquote>
<p>MGSV UI</p>
</blockquote>

<p><img src="/assets/img/splinter-cell-ui.jpg" alt=""></p>

<blockquote>
<p>Splinter Cell UI</p>
</blockquote>

<p>It uses <a href="https://github.com/zivong/jekyll-theme-hamilton">hamilton</a> as a base theme, using CSS from <a href="https://github.com/metakirby5/yorha">https://github.com/metakirby5/yorha</a> to provide styling. There is no dark mode.</p>

<p>I&#39;d read some blogs with neat sidenotes for links, upon researching how to do this I came across references to Edward Tufte. Projects such as <a href="https://github.com/clayh53/tufte-jekyll/tree/master">tufte-jekyll</a> provided the base for this site, as well as some some useful code snippets.</p>

<p>You can see a post highlighting the features and content styles here: <a href="/tufte-style-jekyll-blog/">Tufte style post</a>, plus some custom jekyll plugins i wrote for scrollable + dropdown code blocks. Overall each post should make good use of images of different widths, plus sidenotes, resulting in something like this:</p>

<p><img src="/assets/img/design/example.png" width=600px> </p>

<p>This actually got me reading <a href="https://www.edwardtufte.com/books/">Tufte&#39;s books on design</a>, <em>Visual Explanations</em>, <em>Envisioning Information</em>, and <em>The visual display of quantative information</em>. These are beautifully produced, informative, and a joy to read.</p>

<p>Although I&#39;m not much of a designer I&#39;m proud how this blog&#39;s design has ended up. I do my drawings in <a href="https://excalidraw.com/">Excalidraw</a>, which provides a really intuitive interface that can produce &quot;hand-drawing-y&quot; images. Over time, especially since LLMs have come about, I&#39;ve been trying to incorporate more interactive elements such as embedded HTML diagrams and matplotlib charts, to make it feel less dry.</p>

<p>After quite a lot of time I realised that one of the inspirations for this blog, and my excalidraw-y diagramming style was this book I read as a kid: <a href="https://www.amazon.co.uk/Way-Things-Work-Now/dp/0544824385">The way things work</a>. It explains mechanical and scientific concepts with fun, engaging illustrations, and got me interested in science and technology. The illustrations in there are infitnitely better than mine, but I think I can trace back some of my wanting to understand and draw the shapes and interactions of systems to this. For example check out this amazing illustration of pin &amp; tumbler locks:</p>

<p><img src="/assets/img/design/waythingswork.jpg" alt=""></p>

<p>The illustrations make frequent use of mammoths, a motif that hasn&#39;t made it over to my drawings, but I remember loving the inclusion of mammoths in that book, and the fun it added to diagrams. I found this good article about the books and their illustration that nicely summarises how the humour in the pictures helps readers:</p>

<div class='epigraph'><blockquote><p>The mammoth is us; a little bewildered but trying hard to make its way in a complex world. [...] Like the mammoth, the inventor seeks to humanise our attempts to understand technology and render it comprehensible; even if the answers gained are sometimes wrong. His descriptions are not always right, which on the surface could seem misleading. However, his story is always clearly indicated as a secondary and intentionally humorous tale</p><footer>, <cite> https://www.christopherroosen.com/blog/2021/9/5/david-macaulay-neil-ardley-the-way-things-work </cite></footer></blockquote></div>

<p>Similarly, I&#39;d like to think my tails of failing to solve challenges, and frequent dead ends provide a more approachable narrative in learning how things work. There&#39;s another great quote from that article later on that discusses the method of communicating complex topics with illustrations: </p>

<div class='epigraph'><blockquote><p>The goal of such explanation isn’t to understand every aspect of a machine or technology, but to grasp its core ‘zeitgeist,’ its operating principle. The goal is not for people to be able to build a complex device, like a gearbox or telephone, but to understand how, in principle, these these things work. Crucially, its mean to encourage people to be curious and interested, to want to know more. They need to be comfortable with ever increasing levels of complexity.</p><footer>, <cite>https://www.christopherroosen.com/blog/2021/9/5/david-macaulay-neil-ardley-the-way-things-work</cite></footer></blockquote></div>

<p>I think this is also what makes me draw all the crappy excalidraw images, wanting to break down the systems to their component parts, and put them back together.</p>

<p>Depending on the platform you&#39;re reading from, you may notice the site doesn&#39;t render well on mobile. This is because I hat every second I spend on CSS / HTML, and I often hardcode widths etc as pixel counts in the source. Apologies.</p>]]></content><author><name>Hendo</name></author><category term="junk" /><category term="html" /><category term="design" /><summary type="html"><![CDATA[Design]]></summary></entry><entry><title type="html">Tufte-style Jekyll blog</title><link href="/tufte-style-jekyll-blog/" rel="alternate" type="text/html" title="Tufte-style Jekyll blog" /><published>2020-04-13T04:46:04-05:00</published><updated>2020-04-13T04:46:04-05:00</updated><id>/tufte-style-jekyll-blog</id><content type="html" xml:base="/tufte-style-jekyll-blog/"><![CDATA[<p><span class='newthought'>The Tufte Jekyll theme</span>  is an attempt to create a website design with the look and feel of Edward Tufte&#39;s books and handouts. Tufte’s style is known for its extensive use of sidenotes, tight integration of graphics with text, and well-set typography.&lt;!--more--&gt; The idea for this project is essentially cribbed wholesale from Tufte and R Markdown&#39;s Tufte Handout format<label for='One' class='margin-toggle sidenote-number'></label><input type='checkbox' id='One' class='margin-toggle'/><span class='sidenote'>See <a href="https://tufte-latex.github.io/tufte-latex/">tufte-latex.github.io/tufte-latex/</a> and <a href="http://rmarkdown.rstudio.com/tufte_handout_format.html">rmarkdown.rstudio.com/tufte_handout_format</a> </span> This page is an adaptation of the <a href="http://rmarkdown.rstudio.com/examples/tufte-handout.pdf">Tufte Handout PDF</a>.</p>

<h2 id="custom-stuff">Custom stuff</h2>

<p>dropdown:
<div class="dropdown-code">
  <button class="dropdown-code-toggle" onclick="toggleDropdownCode('dropdown-81262')" aria-expanded="false">
    <span class="dropdown-arrow">▶</span>
    <span class="dropdown-title">Installation steps</span>
  </button>
  <div id="dropdown-81262" class="dropdown-code-content" hidden>
    <div class="highlight"><pre><code class="language-bash" data-lang="bash">git clone <a href="https://github.com/example/repo">https://github.com/example/repo</a>
<span class="nb">cd </span>repo
make <span class="nb">install</span>
</code></pre></div>
  </div>
</div></p>

<p>Scroll:
<div class="scrollable-code" style="max-height: 400px; overflow-y: auto; border: 1px solid #ddd; border-radius: 4px; margin: 1em 0;">
  <div class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="kd">function</span> <span class="nx">test</span><span class="p">()</span> <span class="p">{</span>
    <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">&quot;</span><span class="s2">highlighted JS</span><span class="dl">&quot;</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div>
</div></p>

<h2 id="jekyll-customizations">Jekyll customizations</h2>

<p>This Jekyll blog theme is based on the github repository by Edward Tufte <a href="https://github.com/edwardtufte/tufte-css">here</a>, which was orginally created by Dave Leipmann, but is now labeled under Edward Tufte&#39;s moniker. I borrowed freely from the Tufte-CSS repo and have transformed many of the typographic and page-structural features into a set of custom Liquid tags that make creating content using this style much easier than writing straight HTML. Essentially, if you know markdown, and mix in a few custom Liquid tags, you can be creating a website with this document style in short order.</p>

<p>The remainder of this sample post is a self-documenting survey of the features of the Tufte-Jekyll theme. I have taken almost all of the sample content from the <a href="https://github.com/edwardtufte/tufte-css">Tufte-css</a> repo and embedded it here to illustrate the parity in appearence between the two. The additional verbiage and commentary I have added is to document the custom <em>Liquid</em> markup tags and other features that are bundled with this theme.</p>

<h2 id="side-images">side images</h2>

<p><img src="/assets/img/gping.png" alt="image"></p>

<p>Some text.</p>

<h3 id="the-sass-settings-file">The SASS settings file</h3>

<p>I have taken much of the actual <em>Tufte-css</em> files and modified them as necessary to accomodate the needs inherent in creating a Jekyll theme that has additional writing aids such as the Liquid tags. I have also turned the CSS file into a <a href="http://sass-lang.com">SASS</a> file (the .scss type).  This means that you can alter things like font choices, text color, background color, and underlining style by changing values in this file. When the Jekyll site is built using <code>jekyll build</code> the settings in this file will be compiled into the customized CSS file that the site uses. If you don&#39;t use SCSS or SASS, you are missing out on a huge productivity tool.</p>

<p>This file looks like this:</p>
<div class="highlight"><pre><code class="language-" data-lang="">/* This file contains all the constants for colors and font styles */

$body-font:   ETBembo, Palatino, "Palatino Linotype", "Palatino LT STD", "Book Antiqua", Georgia, serif;
// Note that Gill Sans is the top of the stack and corresponds to what is used in Tufte's books
// However, it is not a free font, so if it is not present on the computer that is viewing the webpage
// The free Google 'Lato' font is used instead. It is similar.
$sans-font:  "Gill Sans", "Gill Sans MT", "Lato", Calibri, sans-serif;
$code-font: Consolas, "Liberation Mono", Menlo, Courier, monospace;
$url-font: "Lucida Console", "Lucida Sans Typewriter", Monaco, "Bitstream Vera Sans Mono", monospace;
$text-color: #111;
$bg-color: #fffff8;
$contrast-color: #a00000;
$border-color: #333333;
$link-style: color; // choices are 'color' or 'underline'. Default is color using $contrast-color set above

</code></pre></div>
<p>Any of these values can be changed in the <code>_sass/_settings.scss</code> file before the site is built. The default values are the ones from <em>tufte-css</em>.</p>

<h2 id="fundamentals">Fundamentals</h2>

<h3 id="color">Color</h3>

<p>Although paper handouts obviously have a pure white background, the web is better served by the use of slightly off-white and off-black colors. I picked <code>#fffff8</code> and <code>#111111</code> because they are nearly indistinguishable from their &#39;pure&#39; cousins, but dial down the harsh contrast. Tufte&#39;s books are a study in spare, minimalist design. In his book <a href="http://www.edwardtufte.com/tufte/books_vdqi">The Visual Display of Quantitative Information</a>, he uses a red ink to add some visual punctuation to the buff colored paper and dark ink. In that spirit, links are styled using a similar red color.</p>

<h3 id="headings">Headings</h3>

<p>Tufte CSS uses <code>&lt;h1&gt;</code> for the document title, <code>&lt;p&gt;</code> with class <code>code</code> for the document subtitle, <code>&lt;h2&gt;</code> for section headings, and <code>&lt;h3&gt;</code> for low-level headings. More specific headings are not encouraged. If you feel the urge to reach for a heading of level 4 or greater, consider redesigning your document:</p>

<blockquote>
<p>[It is] notable that the Feynman lectures (3 volumes) write about all of physics in 1800 pages, using only 2 levels of hierarchical headings: chapters and A-level heads in the text. It also uses the methodology of <em>sentences</em> which then cumulate sequentially into <em>paragraphs</em>, rather than the grunts of bullet points. Undergraduate Caltech physics is very complicated material, but it didn’t require an elaborate hierarchy to organize.</p>
</blockquote>

<p><cite><a href="http://www.edwardtufte.com/bboard/q-and-a-fetch-msg?msg_id=0000hB">http://www.edwardtufte.com/bboard/q-and-a-fetch-msg?msg_id=0000hB</a></cite></p>

<p>As a bonus, this excerpt regarding the use of headings provides an example of using block quotes. Markdown does not have a native <code>&lt;cite&gt;</code> shorthand, but real html can be sprinkled in with the Markdown text. In the previous example, the <code>&lt;cite&gt;</code> was preceded with a single return after the quotation itself. The previous blockquote was written in Markdown thusly:</p>
<div class="highlight"><pre><code class="language-Liquid" data-lang="Liquid">[It is] notable that the Feynman lectures (3 volumes) write about all of physics in 1800 pages, using only 2 levels of hierarchical headings: chapters and A-level heads in the text. It also uses the methodology of *sentences* which then cumulate sequentially into *paragraphs*, rather than the grunts of bullet points. Undergraduate Caltech physics is very complicated material, but it didn’t require an elaborate hierarchy to organize.
&lt;cite&gt;[http://www.edwardtufte.com/bboard/q-and-a-fetch-msg?msg_id=0000hB](http://www.edwardtufte.com/bboard/q-and-a-fetch-msg?msg_id=0000hB)&lt;/cite&gt;

</code></pre></div>
<p><span class='newthought'>In his later books</span> <label for='two' class='margin-toggle sidenote-number'></label><input type='checkbox' id='two' class='margin-toggle'/><span class='sidenote'><a href="http://www.edwardtufte.com/tufte/books_be">http://www.edwardtufte.com/tufte/books_be</a> </span>, Tufte starts each section with a bit of vertical space, a non-indented paragraph, and sets the first few words of the sentence in small caps. To accomplish this using this style, enclose the sentence fragment you want styled with small caps in a custom Liquid tag called &#39;newthought&#39; like so:</p>
<div class="highlight"><pre><code class="language-Liquid" data-lang="Liquid">{% newthought 'In his later books' %}

</code></pre></div>
<h3 id="text">Text</h3>

<p>In print, Tufte uses the proprietary Monotype Bembo<label for='3' class='margin-toggle sidenote-number'></label><input type='checkbox' id='3' class='margin-toggle'/><span class='sidenote'>See Tufte’s comment in the <a href="http://www.edwardtufte.com/bboard/q-and-a-fetch-msg?msg_id=0000Vt">Tufte book fonts</a> thread. </span> font. A similar effect is achieved in digital formats with the now open-source ETBembo, which Tufte-Jekyll supplies with a <code>@font-face</code> reference to a .ttf file. Thanks to <a href="https://github.com/daveliepmann/tufte-css/commit/0a810a7d5f4707941c6f9fe99a53ec41f50a5c00">Linjie Ding</a>, italicized text uses the ETBembo Italic font instead of mechanically skewing the characters. In case ETBembo somehow doesn’t work, Tufte CSS degrades gracefully to other serif fonts like Palatino and Georgia. Notice that Tufte CSS includes separate font files for <strong>bold</strong> (strong) and <em>italic</em> (emphasis), instead of relying on the browser to mechanically transform the text. This is typographic best practice. It’s also really important. Thus concludes my unnecessary use of em and strong for the purpose of example.</p>

<p>Code snippets ape GitHub&#39;s font selection using Microsoft&#39;s <a href="http://www.microsoft.com/typography/ClearTypeFonts.mspx"><em>Consolas</em></a> and the sans-serif font uses Tufte&#39;s choice of Gill Sans. Since this is not a free font, and some systems will not have it installed, the free google font <a href="https://www.google.com/fonts/specimen/Lato"><em>Lato</em></a> is designated as a fallback.</p>

<h2 id="epigraphs">Epigraphs</h2>

<div class='epigraph'><blockquote><p>The English language . . . becomes ugly and inaccurate because our thoughts are foolish, but the slovenliness of our language makes it easier for us to have foolish thoughts.</p><footer>George Orwell, <cite> "Politics and the English Language" </cite></footer></blockquote></div>

<div class='epigraph'><blockquote><p>For a successful technology, reality must take precedence over public relations, for Nature cannot be fooled.</p><footer>Richard P. Feynman, <cite> “What Do You Care What Other People Think?” </cite></footer></blockquote></div>

<p>If you’d like to introduce your page or a section of your page with some quotes, use epigraphs. The two examples above show how they are styled. Epigraph elements are modeled after chapter epigraphs in Tufte’s books (particularly <em>Beautiful Evidence</em>). The <a href="https://github.com/edwardtufte/tufte-css">Tufte-css</a> gitub repository has detailed instructions on how to achieve this using HTML elements. As an easier alternative, the <em>Tufte-jekyll</em> theme uses custom <em>Liquid tag</em> pairs that allow the writer to embed elements such as epigraphs in the middle of the regular Markdown text being edited. </p>

<p>In order to use an epigraph in a page or section, type this:</p>

<p><code>{% epigraph &#39;text of citation&#39; &#39;author of citation&#39; &#39;citation source&#39;  %}</code></p>

<p>to produce this:</p>

<!-- 
<div class='epigraph'><blockquote><p>I do not paint things, I paint only the differences between things.</p><footer>Henri Matisse, <cite>Henri Matisse Dessins: thèmes et variations, 1943</cite></footer></blockquote></div>

<div class='epigraph'><blockquote><p> "How did you go bankrupt?" Two ways. Gradually, then suddenly.</p><footer>Ernest Hemingway, <cite> "The Sun Also Rises" </cite></footer></blockquote></div>
 -->

<h3 id="lists">Lists</h3>

<p>Tufte points out that while lists have valid uses, they tend to promote ineffective writing habits due to their “lack of syntactic and intellectual discipline”. He is particularly critical of hierarchical and bullet-pointed lists. So before reaching for an HTML list element, ask yourself:</p>

<ul>
<li>Does this list actually have to be represented using an HTML ul or ol element?</li>
<li>Would my idea be better expressed as sentences in paragraphs?</li>
<li>Is my message causally complex enough to warrant a flow diagram instead?</li>
</ul>

<p>This is but a small subset of a proper overview of the topic of lists in communication. A better way to understand Tufte’s thoughts on lists would be to read “The Cognitive Style of PowerPoint: Pitching Out Corrupts Within,” a chapter in Tufte’s book <em>Beautiful Evidence</em>, excerpted at some length by Tufte himself <a href="http://www.edwardtufte.com/bboard/q-and-a-fetch-msg?msg_id=0002QF">on his website</a>. The whole piece is information-dense and therefore difficult to summarize. He speaks to web design specifically, but in terms of examples and principles rather than as a set of simple do-this, don’t-do-that prescriptions. It is well worth reading in full for that reason alone.</p>

<p>For these reasons, Tufte CSS encourages caution before reaching for a list element, and by default removes the bullet points from unordered lists.</p>

<h2 id="figures">Figures</h2>

<h3 id="margin-figures">Margin Figures</h3>

<p><label for='mf-id-1' class='margin-toggle'>&#8853;</label><input type='checkbox' id='mf-id-1' class='margin-toggle'/><span class='marginnote'><img class='fullwidth' src='/assets/img/tufte/rhino.png'/><br>F.J. Cole, “The History of Albrecht Dürer’s Rhinoceros in Zoological Literature,” <em>Science, Medicine, and History: Essays on the Evolution of Scientific Thought and Medical Practice</em> (London, 1953), ed. E. Ashworth Underwood, 337-356. From page 71 of Edward Tufte’s <em>Visual Explanations</em>.</span></p>

<p>Images and graphics play an integral role in Tufte’s work. To place figures in the margin, use the custom margin figure liquid tag included in the <code>_plugins</code> directory like so:</p>

<p><code>{% marginfigure &#39;mf-id-whatever&#39; &#39;assets/img/tufte/rhino.png&#39; &#39;F.J. Cole, “The History of Albrecht Dürer’s Rhinoceros in Zoological Literature,” *Science, Medicine, and History: Essays on the Evolution of Scientific Thought and Medical Practice* (London, 1953), ed. E. Ashworth Underwood, 337-356. From page 71 of Edward Tufte’s *Visual Explanations*.&#39;  %}</code>.</p>

<p>Note that this tag has <em>three</em> parameters. The first is an arbitrary id. This parameter can be named anything as long as it is unique to this post. The second parameter is the path to the image. And the final parameter is whatever caption you want to be displayed with the figure.  All parameters <em>must</em> be enclosed in quotes for this simple liquid tag to work! </p>

<p>In this example, the <em>Liquid</em> marginfigure tag was inserted <em>before</em> the paragraph so that it aligns with the beginning of the paragraph. On small screens, the image will collapse into a small symbol: <span class="contrast ">&#8853;</span> at the location it has been inserted in the manuscript. Clicking on it will open the image.</p>

<h3 id="full-width-figures">Full Width Figures</h3>

<p>If you need a full-width image or figure, another custom liquid tag is available to use. Oddly enough, it is named &#39;fullwidth&#39;, and this markup:</p>

<p><code>{% fullwidth &#39;assets/img/tufte/napoleons-march.png&#39; &#39;Napoleon&#39;s March *(Edward Tufte’s English translation)*&#39;  %}</code></p>

<p>Yields this:</p>

<figure class='fullwidth'><img src='/assets/img/tufte/napoleons-march.png'/><figcaption>Napoleon’s March <em>(Edward Tufte’s English translation)</em></figcaption></figure>

<h3 id="main-column-figures">Main Column Figures</h3>

<p>Besides margin and full width figures, you can of course also include figures constrained to the main column. Yes, you guessed it, a custom liquid tag rides to the rescue once again:</p>

<p><code>{% maincolumn &#39;assets/img/tufte/export-imports.png&#39; &#39;From Edward Tufte, *Visual Display of Quantitative Information*, page 92&#39;  %}</code></p>

<p>yields this:</p>

<figure><img src='/assets/img/tufte/exports-imports.png'/><figcaption class='maincolumn-figure'>From Edward Tufte, <em>Visual Display of Quantitative Information</em>, page 92</figcaption></figure>

<h2 id="sidenotes-and-margin-notes">Sidenotes and Margin notes</h2>

<p>One of the most prominent and distinctive features of Tufte&#39;s style is the extensive use of sidenotes and margin notes. Perhaps you have noticed their use in this document already. You are very astute.</p>

<p>There is a wide margin to provide ample room for sidenotes and small figures. There exists a slight semantic distinction between <em>sidenotes</em> and <em>marginnotes</em>.</p>

<h3 id="sidenotes">Sidenotes</h3>

<p>Sidenotes<label for='sn-id-whatever' class='margin-toggle sidenote-number'></label><input type='checkbox' id='sn-id-whatever' class='margin-toggle'/><span class='sidenote'>This is a sidenote and <em>displays a superscript</em> </span> display a superscript. The <em>sidenote</em> Liquid tag contains two components. The first is an identifier allowing the sidenote to be targeted by the twitchy index fingers of mobile device users so that all the yummy sidenote goodness is revealed when the superscript is tapped. The second components is the actual content of the sidenote. Both of these components should be enclosed in single quotes. Note that we are using the CSS &#39;counter&#39; trick to automagically keep track of the number sequence on each page or post. On small screens, the sidenotes disappear and when the superscript is clicked, a side note will open below the content, which can then be closed with a similar click. Here is the markup for the sidenote at the beginning of this paragraph:</p>

<p><code>{% sidenote &#39;sn-id-whatever&#39; &#39;This is a sidenote and *displays a superscript*&#39;%}</code></p>

<h3 id="margin-notes">Margin notes</h3>

<p>Margin notes<label for='mn-id-whatever' class='margin-toggle'> &#8853;</label><input type='checkbox' id='mn-id-whatever' class='margin-toggle'/><span class='marginnote'>This is a margin note <em>without</em> a superscript </span> are similar to sidenotes, but do not display a superscript. The <em>marginnnote</em> Liquid tags has the same two components as the <em>sidenote</em> tag. Anything can be placed in a margin note. Well, anything that is an inline element. Block level elements can make the Kramdown parser do strange things. On small screens, the margin notes disappear and this symbol: <span class="contrast ">&#8853;</span> pops up. When clicked, it will open the margin note below the content, which can then be closed with a similar click. The Markdown content has a similar sort of markup as a sidenote, but without a number involved:</p>

<p><code>{% marginnote &#39;mn-id-whatever&#39; &#39;This is a margin note *without* a superscript&#39; %}</code></p>

<h2 id="equations">Equations</h2>

<p>The Markdown parser being used by this Jekyll theme is Kramdown, which contains some built-in <a href="//www.mathjax.org">Mathjax</a> support. Both inline and block-level mathematical figures can be added to the content.</p>

<p>For instance, the following inline sequence:</p>

<p><em>When \$\$ a \ne 0 \$\$, there are two solutions to \$\$ ax^2 + bx + c = 0 \$\$</em></p>

<p>is written by enclosing a Mathjax expression within <em>a matching pair of double dollar signs: <code>$$</code></em>:</p>

<p><code>When $$ a \ne 0 $$, there are two solutions to $$ ax^2 + bx + c = 0 $$</code></p>

<p>Similarly, this block-level Mathjax expression:</p>

<p>\$\$ x = {-b \pm \sqrt{b^2-4ac} \over 2a} \$\$</p>

<p>is written by enclosing the expression within a pair of <code>$$</code> with an empty line above and below:</p>

<p><code>$$ x = {-b \pm \sqrt{b^2-4ac} \over 2a} $$</code></p>

<p>You can get pretty fancy, for instance, the wave equation&#39;s nabla is no big thing:</p>

<p>\$\$ \frac{\partial^2 y}{\partial t^2}= c^2\nabla^2u \$\$</p>

<p>All of the standard <span class="latex">L<sup>a</sup>T<sub>e</sub>X</span> equation markup is available to use inside these block tags.</p>

<p>Please note that the block-level Mathjax expressions <em>must</em> be on their own line, separated from content above and below the block by a blank line for the Kramdown parser and the Mathjax javascript to play nicely with one another.</p>

<p>The Mathjax integration is tricky, and some things such as the inline matrix notation simply do not work well unless allowances are made for using the notation for a small matrix. Bottom line: If you are using this to document mathematics, be super careful to isolate your <span class="latex">L<sup>a</sup>T<sub>e</sub>X</span> blocks by blank lines!  </p>

<h2 id="tables">Tables</h2>

<p>Tables are, frankly,  a pain in the ass to create. That said, they often are one of the best methods for presenting data. Tabular data are normally presented with right-aligned numbers, left-aligned text, and minimal grid lines.</p>

<p>Note that when writing Jekyll Markdown content, there will often be a need to get some dirt under your fingernails and stoop to writing a little honest-to-god html. Yes, all that hideous <code>&lt;table&gt;..&lt;thead&gt;..&lt;th&gt;</code> nonsense. <em>And</em> you must wrap the unholy mess in a <code>&lt;div class=&quot;table-wrapper&quot;&gt;</code> tag to ensure that the table stays centered in the main content column.</p>

<p>Tables are designed with an <code>overflow:scroll</code> property to create slider bars when the viewport is narrow. This is so that you do not collapse all your beautiful data into a jumble of letters and numbers when you view it on your smartphone.</p>

<p><label for='table-1-id' class='margin-toggle'> &#8853;</label><input type='checkbox' id='table-1-id' class='margin-toggle'/><span class='marginnote'><em>Table 1</em>: A table with default style formatting </span>
<div class="table-wrapper">
  <table class="table-alpha" id="newspaper-tone">
    <thead>
      <tr>
        <th class="left">Content and tone of front-page articles in 94 U.S. newspapers, October and November, 1974</th>
        <th class="left">Number of articles</th>
        <th>Percent of articles with negative criticism of specific person or policy</th></tr>
    </thead>
    <tbody>
      <tr>
        <td class="text">Watergate: defendants and prosecutors, Ford’s pardon of Nixon</td>
        <td><div class="number">537</div></td>
        <td class="c"><div class="number">49%</div></td>
      </tr>
      <tr>
        <td class="text">Inflation, high cost of living</td>
        <td><div class="number">415</div></td>
        <td class="c"><div class="number">28%</div></td>
      </tr>
      <tr>
        <td class="text">Government competence: costs, quality, salaries of public employees</td>
        <td><div class="number">322</div></td>
        <td class="c"><div class="number">30%</div></td>
      </tr>
      <tr>
        <td class="text">Confidence in government: power of special interests, trust in political leaders, dishonesty in politics</td>
        <td><div class="number">266</div></td>
        <td class="c"><div class="number">52%</div></td>
      </tr>
      <tr>
        <td class="text">Government power: regulation of business, secrecy, control of CIA and FBI</td>
        <td><div class="number">154</div></td>
        <td class="c"><div class="number">42%</div></td>
      </tr>
      <tr>
        <td class="text">Crime</td>
        <td><div class="number">123</div></td>
        <td class="c"><div class="number r">30%</div></td>
      </tr>
      <tr>
        <td class="text">Race</td>
        <td><div class="number">103</div></td>
        <td class="c"><div class="number">25%</div></td>
      </tr>
      <tr>
        <td class="text">Unemployment</td>
        <td><div class="number">100</div></td>
        <td class="c"><div class="number">13%</div></td>
      </tr>
      <tr>
        <td class="text">Shortages: energy, food</td>
        <td><div class="number">68</div></td>
        <td class="c"><div class="number">16%</div></td>
      </tr>
    </tbody>
  </table>
</div></p>

<p>This is not the One True Table. Such a style does not exist. One must craft each data table with custom care to the narrative one is telling with that specific data. So take this not as “the table style to use”, but rather as “a table style to start from”. From here, use principles to guide you: avoid chartjunk, optimize the data-ink ratio (“within reason”, as Tufte says), and “mobilize every graphical element, perhaps several times over, to show the data.<label for='table-id' class='margin-toggle sidenote-number'></label><input type='checkbox' id='table-id' class='margin-toggle'/><span class='sidenote'>Page 139, <em>The Visual Display of Quantitative Information</em>, Edward Tufte 2001. </span> Furthermore, one must know when to reach for more complex data presentation tools, like a custom graphic or a JavaScript charting library.</p>

<p>As an example of alternative table styles, academic publications written in <span class="latex">L<sup>a</sup>T<sub>e</sub>X</span> often rely on the <code>booktabs</code> package to produce clean, clear tables. Similar results can be achieved in Tufte CSS with the <code>booktabs</code> class, as demonstrated in this table:</p>

<p><label for='table-2-id' class='margin-toggle'> &#8853;</label><input type='checkbox' id='table-2-id' class='margin-toggle'/><span class='marginnote'><em>Table 2</em>: A table with booktabs style formatting </span>
<div class="table-wrapper">
<table class="booktabs">
          <thead>
            <tr><th colspan="2" class="cmid">Items</th><th class="nocmid"></th></tr>
            <tr><th class="l">Animal</th><th>Description</th><th class="r">Price ($)</th></tr>
          </thead>
          <tbody>
            <tr><td>Gnat</td>     <td>per gram</td><td class="r">13.65</td></tr>
            <tr><td></td>         <td>each</td>    <td class="r">0.01</td></tr>
            <tr><td>Gnu</td>      <td>stuffed</td> <td class="r">92.50</td></tr>
            <tr><td>Emu</td>      <td>stuffed</td> <td class="r">33.33</td></tr>
            <tr><td>Armadillo</td><td>frozen</td>  <td class="r">8.99</td></tr>
          </tbody>
</table>
</div></p>

<p>The table above was written in HTML as follows:</p>
<div class="highlight"><pre><code class="language-" data-lang="">&lt;div class="table-wrapper"&gt;
&lt;table class="booktabs"&gt;
          &lt;thead&gt;
            &lt;tr&gt;&lt;th colspan="2" class="cmid"&gt;Items&lt;/th&gt;&lt;th class="nocmid"&gt;&lt;/th&gt;&lt;/tr&gt;
            &lt;tr&gt;&lt;th class="l"&gt;Animal&lt;/th&gt;&lt;th&gt;Description&lt;/th class="r"&gt;&lt;th&gt;Price ($)&lt;/th&gt;&lt;/tr&gt;
          &lt;/thead&gt;
          &lt;tbody&gt;
            &lt;tr&gt;&lt;td&gt;Gnat&lt;/td&gt;     &lt;td&gt;per gram&lt;/td&gt;&lt;td class="r"&gt;13.65&lt;/td&gt;&lt;/tr&gt;
            &lt;tr&gt;&lt;td&gt;&lt;/td&gt;         &lt;td&gt;each&lt;/td&gt;    &lt;td class="r"&gt;0.01&lt;/td&gt;&lt;/tr&gt;
            &lt;tr&gt;&lt;td&gt;Gnu&lt;/td&gt;      &lt;td&gt;stuffed&lt;/td&gt; &lt;td class="r"&gt;92.50&lt;/td&gt;&lt;/tr&gt;
            &lt;tr&gt;&lt;td&gt;Emu&lt;/td&gt;      &lt;td&gt;stuffed&lt;/td&gt; &lt;td class="r"&gt;33.33&lt;/td&gt;&lt;/tr&gt;
            &lt;tr&gt;&lt;td&gt;Armadillo&lt;/td&gt;&lt;td&gt;frozen&lt;/td&gt;  &lt;td class="r"&gt;8.99&lt;/td&gt;&lt;/tr&gt;
          &lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;

</code></pre></div>
<p><span class='newthought'>I like this style of table,</span>   so I have made it the default for unstyled tables. This allows use of the <a href="https://michelf.ca/projects/php-markdown/extra/"><em>Markdown Extra</em></a> features built into the <a href="http://kramdown.gettalong.org/parser/kramdown.html"><em>Kramdown</em></a> parser. Here is a table created using the Markdown Extra table syntax to make a nice table which has the side benefit of being human readable in the raw Markdown file:</p>

<p><label for='tableID-3' class='margin-toggle'> &#8853;</label><input type='checkbox' id='tableID-3' class='margin-toggle'/><span class='marginnote'>Table 3: a table created with <em>Markdown Extra</em> markup using only the default table styling </span></p>

<table><thead>
<tr>
<th style="text-align: left"></th>
<th style="text-align: right">mpg</th>
<th style="text-align: right">cyl</th>
<th style="text-align: right">disp</th>
<th style="text-align: right">hp</th>
<th style="text-align: right">drat</th>
<th style="text-align: right">wt</th>
</tr>
</thead><tbody>
<tr>
<td style="text-align: left">Mazda RX4</td>
<td style="text-align: right">21</td>
<td style="text-align: right">6</td>
<td style="text-align: right">160</td>
<td style="text-align: right">110</td>
<td style="text-align: right">3.90</td>
<td style="text-align: right">2.62</td>
</tr>
<tr>
<td style="text-align: left">Mazda RX4 Wag</td>
<td style="text-align: right">21</td>
<td style="text-align: right">6</td>
<td style="text-align: right">160</td>
<td style="text-align: right">110</td>
<td style="text-align: right">3.90</td>
<td style="text-align: right">2.88</td>
</tr>
<tr>
<td style="text-align: left">Datsun 710</td>
<td style="text-align: right">22.8</td>
<td style="text-align: right">4</td>
<td style="text-align: right">108</td>
<td style="text-align: right">93</td>
<td style="text-align: right">3.85</td>
<td style="text-align: right">2.32</td>
</tr>
<tr>
<td style="text-align: left">Hornet 4 Drive</td>
<td style="text-align: right">21.4</td>
<td style="text-align: right">6</td>
<td style="text-align: right">258</td>
<td style="text-align: right">110</td>
<td style="text-align: right">3.08</td>
<td style="text-align: right">3.21</td>
</tr>
<tr>
<td style="text-align: left">Hornet Sportabout</td>
<td style="text-align: right">18.7</td>
<td style="text-align: right">8</td>
<td style="text-align: right">360</td>
<td style="text-align: right">175</td>
<td style="text-align: right">3.15</td>
<td style="text-align: right">3.44</td>
</tr>
<tr>
<td style="text-align: left">Valiant</td>
<td style="text-align: right">18.1</td>
<td style="text-align: right">6</td>
<td style="text-align: right">160</td>
<td style="text-align: right">105</td>
<td style="text-align: right">2.76</td>
<td style="text-align: right">3.46</td>
</tr>
</tbody></table>

<p>Using the following Markdown formatting:</p>
<div class="highlight"><pre><code class="language-" data-lang="">|                 |mpg  | cyl  |  disp  |   hp   |  drat  | wt  |
|:----------------|----:|-----:|-------:|-------:|-------:|----:|
|Mazda RX4        |21   |6     |160     |110     |3.90    |2.62 |
|Mazda RX4 Wag    |21   |6     |160     |110     |3.90    |2.88 |
|Datsun 710       |22.8 |4     |108     |93      |3.85    |2.32 |
etc...

</code></pre></div>
<p>The following is a more simple table, showing the Markdown-style table markup. Remember to label the table with a <em>marginnote</em> Liquid tag, and you <em>must</em> separate the label from the table with a single blank line. This markup:</p>
<div class="highlight"><pre><code class="language-" data-lang="">{% marginnote 'Table-ID4' 'Table 4: a simple table showing left, center, and right alignment of table headings and data'  %}

|**Left** |**Center**|**Right**|
|:--------|:--------:|--------:|
 Aardvarks|         1|$3.50
       Cat|   5      |$4.23
  Dogs    |3         |$5.29

</code></pre></div>
<p>Yields this table:</p>

<p><label for='Table-ID4' class='margin-toggle'> &#8853;</label><input type='checkbox' id='Table-ID4' class='margin-toggle'/><span class='marginnote'>Table 4: a simple table showing left, center, and right alignment of table headings and data </span></p>

<table><thead>
<tr>
<th style="text-align: left">**Left**</th>
<th style="text-align: center">**Center**</th>
<th style="text-align: right">**Right**</th>
</tr>
</thead><tbody>
<tr>
<td style="text-align: left">Aardvarks</td>
<td style="text-align: center">1</td>
<td style="text-align: right">\$3.50</td>
</tr>
<tr>
<td style="text-align: left">Cat</td>
<td style="text-align: center">5</td>
<td style="text-align: right">\$4.23</td>
</tr>
<tr>
<td style="text-align: left">Dogs</td>
<td style="text-align: center">3</td>
<td style="text-align: right">\$5.29</td>
</tr>
</tbody></table>

<h2 id="code">Code</h2>

<p>Code samples use a monospace font using the &#39;code&#39; class. The Kramdown parser has the &#39;GFM&#39; option enabled, which stands for &#39;Github Flavored Markdown&#39;, and this means that both inline code such as <code>#include &lt;stdio.h&gt;</code> and blocks of code can be delimited by surrounding them with 3 backticks:</p>
<div class="highlight"><pre><code class="language-" data-lang="">(map tufte-style all-the-things)

</code></pre></div>
<p>is created by the following markup:
<pre><code><code>(map tufte-style all-the-things)</code></code></pre></p>

<p>To get the code highlighted in the language of your choice like so:</p>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="k">module</span> <span class="nn">Jekyll</span>
  <span class="k">class</span> <span class="nc">RenderFullWidthTag</span> <span class="o">&lt;</span> <span class="no">Liquid</span><span class="o">::</span><span class="no">Tag</span>
  <span class="nb">require</span> <span class="s2">"shellwords"</span>

    <span class="k">def</span> <span class="nf">initialize</span><span class="p">(</span><span class="n">tag_name</span><span class="p">,</span> <span class="n">text</span><span class="p">,</span> <span class="n">tokens</span><span class="p">)</span>
      <span class="k">super</span>
      <span class="vi">@text</span> <span class="o">=</span> <span class="n">text</span><span class="p">.</span><span class="nf">shellsplit</span>
    <span class="k">end</span>

    <span class="k">def</span> <span class="nf">render</span><span class="p">(</span><span class="n">context</span><span class="p">)</span>
      <span class="s2">"&lt;div&gt;&lt;img class='fullwidth' src='</span><span class="si">#{</span><span class="vi">@text</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="si">}</span><span class="s2">'/&gt;&lt;/div&gt; "</span> <span class="o">+</span>
      <span class="s2">"&lt;p&gt;&lt;span class='marginnote'&gt;</span><span class="si">#{</span><span class="vi">@text</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span><span class="si">}</span><span class="s2">&lt;/span&gt;&lt;/p&gt;"</span>
    <span class="k">end</span>
  <span class="k">end</span>
<span class="k">end</span>

<span class="no">Liquid</span><span class="o">::</span><span class="no">Template</span><span class="p">.</span><span class="nf">register_tag</span><span class="p">(</span><span class="s1">'fullwidth'</span><span class="p">,</span> <span class="no">Jekyll</span><span class="o">::</span><span class="no">RenderFullWidthTag</span><span class="p">)</span>

</code></pre></div>
<p>Enclose the code block in three backticks, followed by a space and then the language name, like this:</p>

<pre> <code>``` ruby
    module Jekyll
    blah, blah...
   ```</code> </pre>

<p>Jekyll also offers powerful support for code snippets:</p>

<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="k">def</span> <span class="nf">print_hi</span><span class="p">(</span><span class="nb">name</span><span class="p">)</span>
  <span class="nb">puts</span> <span class="s2">"Hi, </span><span class="si">#{</span><span class="nb">name</span><span class="si">}</span><span class="s2">"</span>
<span class="k">end</span>
<span class="n">print_hi</span><span class="p">(</span><span class="s1">'Tom'</span><span class="p">)</span>
<span class="c1">#=&gt; prints 'Hi, Tom' to STDOUT.</span></code></pre></figure>]]></content><author><name>Hendo</name></author><category term="design" /><category term="html" /><summary type="html"><![CDATA[The Tufte Jekyll theme is an attempt to create a website design with the look and feel of Edward Tufte&#39;s books and handouts. Tufte’s style is known for its extensive use of sidenotes, tight integration of graphics with text, and well-set typography.&lt;!--more--&gt; The idea for this project is essentially cribbed wholesale from Tufte and R Markdown&#39;s Tufte Handout formatSee tufte-latex.github.io/tufte-latex/ and rmarkdown.rstudio.com/tufte_handout_format This page is an adaptation of the Tufte Handout PDF.]]></summary></entry></feed>