<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.2.1">Jekyll</generator><link href="https://ivanludvig.dev/feed.xml" rel="self" type="application/atom+xml" /><link href="https://ivanludvig.dev/" rel="alternate" type="text/html" /><updated>2026-10-03T14:26:10+00:00</updated><id>https://ivanludvig.dev/feed.xml</id><title type="html">Ivan Ludvig’s Blog</title><author><name>IvanLudvig</name><email>web@ivanludvig.dev</email></author><entry><title type="html">Neural Cellular Automata with WebGPU</title><link href="https://ivanludvig.dev/tech/neural-ca-webgpu" rel="alternate" type="text/html" title="Neural Cellular Automata with WebGPU" /><published>2026-06-10T11:00:00+00:00</published><updated>2026-06-10T11:00:00+00:00</updated><id>https://ivanludvig.dev/tech/neural-ca-webgpu</id><content type="html" xml:base="https://ivanludvig.dev/tech/neural-ca-webgpu"><![CDATA[<style>
    .demo-link {
        height: 20px;
        display: flex;
        gap: 4px;
        background-color: #fff;
        border: 1px solid #51942a;
        color: #51942a !important;
        text-decoration: none !important;
        padding: 2px 4px;
        font-size: 14px;
        line-height: 16px;
        box-sizing: border-box;
    }

    .demo-link img {
        height: 100%;
    }

    .shield-container {
        display: flex;
        flex-wrap: nowrap;
        justify-content: center;
        align-items: center;
        gap: 16px;
        margin-bottom: 16px;
    }

    .shield-container > a:not(.demo-link) > img {
        display: block;
        height: 20px;
    }

    .growth-img {
        margin-bottom: 16px;
        max-width: 480px;
        width: 100%;
    }
</style>

<div class="shield-container">
    <a href="https://github.com/IvanLudvig/neural-ca-webgpu" target="_blank">
        <img src="https://img.shields.io/badge/GitHub-24292f.svg?logo=github&amp;style=flat-square" alt="GitHub" />
    </a>
    <a href="/neural-ca-webgpu" target="_blank" class="demo-link">
        <img src="../favicon.ico" alt="demo" />
        demo
    </a>
</div>

<center>
    <img src="/assets/img/work/nca-webgpu-cactus.png" alt="NCA cactus growth stages" class="growth-img" />
</center>

<p>A WebGPU demo of <a href="https://doi.org/10.23915/distill.00023">Growing Neural Cellular Automata</a> (Mordvintsev et al., 2020). Each cell in a 40x40 grid runs a shared pretrained neural network in a compute shader to update its state. The CA grows from a single seed cell and self-repairs when damaged.</p>

<h3 id="cell-state-and-perception">Cell State and Perception</h3>

<p>Each cell has a 16-channel state vector: RGBA + 12 hidden channels. The simulation seeds a single center cell (RGB=0, alpha and hidden channels = 1) and lets the network grow outward from there.</p>

<p>Each cell builds a 48-dim input by convolving its 3×3 neighborhood with three fixed kernels: identity, Sobel-X, Sobel-Y across all 16 channels. The Sobel filters give the network gradient information without requiring it to learn spatial awareness from scratch.</p>

<h3 id="network-and-update-rule">Network and Update Rule</h3>

<p>Two linear layers output a delta added to the current state. Network weights are uploaded to GPU storage buffers once and shared read-only across all cells per step.</p>

<p>Two mechanisms from the paper govern the simulation: stochastic updates (each cell fires with 50% probability) and a life mask (cells with no alive neighbors are zeroed out).</p>

<h3 id="webgpu-pipeline">WebGPU Pipeline</h3>

<p>Compute and render passes run back-to-back each frame. Double-buffered state avoids read-write collisions.</p>

<p>You can draw on the canvas to erase cells. The pattern self-repairs: a core property of the neural CA.</p>]]></content><author><name>IvanLudvig</name><email>web@ivanludvig.dev</email></author><category term="tech" /><summary type="html"><![CDATA[A WebGPU demo of Growing Neural Cellular Automata. Each cell runs inference through a shared pretrained neural network entirely in compute shaders]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://ivanludvig.dev/assets/img/work/nca-webgpu-cactus.png" /><media:content medium="image" url="https://ivanludvig.dev/assets/img/work/nca-webgpu-cactus.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Warsaw</title><link href="https://ivanludvig.dev/travel/2026/warsaw" rel="alternate" type="text/html" title="Warsaw" /><published>2026-05-11T11:00:00+00:00</published><updated>2026-05-11T11:00:00+00:00</updated><id>https://ivanludvig.dev/travel/2026/warsaw</id><content type="html" xml:base="https://ivanludvig.dev/travel/2026/warsaw"><![CDATA[<div>
    <div id="Warsaw-country-map-container" style="max-height: 396px; overflow: hidden; transition: max-height .25s; margin-bottom: 16px; z-index: 2; border-radius: 6px;">
        <div id="Warsaw-country-map" style="height: 480px; max-height: 40vh; width: 100%;  z-index: 2;"></div>
    </div>

    <script>
        renderCountryMap('Warsaw');
    </script>
</div>
<center>
    <img src="/assets/img/travel/2026/pl/warsaw/1.jpg" />
    <div class="image-margin"></div>
    <img src="/assets/img/travel/2026/pl/warsaw/2.jpg" />
    <div class="image-margin"></div>
</center>]]></content><author><name>IvanLudvig</name><email>web@ivanludvig.dev</email></author><category term="travel" /><summary type="html"><![CDATA[]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://ivanludvig.dev/assets/img/travel/2026/pl/warsaw/1.jpg" /><media:content medium="image" url="https://ivanludvig.dev/assets/img/travel/2026/pl/warsaw/1.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Hacking Google Random Number Generator (Part 2)</title><link href="https://ivanludvig.dev/tech/google-random-v2" rel="alternate" type="text/html" title="Hacking Google Random Number Generator (Part 2)" /><published>2026-04-20T11:00:00+00:00</published><updated>2026-04-20T11:00:00+00:00</updated><id>https://ivanludvig.dev/tech/google-random-v2</id><content type="html" xml:base="https://ivanludvig.dev/tech/google-random-v2"><![CDATA[<style>
    .shield-container {
        display: flex;
        justify-content: start;
        align-items: center;
        gap: 16px;
        margin-bottom: 16px;
    }
    .float-right-image {
        float: right;
        margin: 0 0 16px 16px;
        text-align: center;
        max-width: 260px;
    }
    .float-right-image img {
        width: 100%;
    }
    @media (max-width: 600px) {
        .float-right-image {
            float: none;
            max-width: 320px;
            margin: 0 auto 16px;
        }
    }
</style>

<div class="shield-container">
    <a href="https://github.com/IvanLudvig/google-rng-hack" target="_blank">
        <img src="https://img.shields.io/badge/GitHub-24292f.svg?logo=github&amp;style=flat-square" alt="GitHub" />
    </a>
</div>

<blockquote>
  <p>Confused? See <a href="/tech/google-random">Part 1</a>.</p>
</blockquote>

<p>Remember that <a href="/tech/google-random">time</a> when I cracked the Google random number generator? Rumour has it that it stopped working: I’ve been told that the code is slow, the numbers are wrong, it ruined someone’s well-rehearsed party trick in front of their crush, bankrupted a small business selling “intricate magic boxes”, caused a train delay and consequently a minor, albeit heated, diplomatic incident, among other things.</p>

<h3 id="recap">Recap</h3>

<p>The original method recovered the 128-bit xorshift state from consecutive <code class="language-plaintext highlighter-rouge">Math.random()</code> outputs by feeding a constraint system into Z3. Two things have since changed: how the internal state is converted to a double and the xorshift algorithm itself.</p>

<h3 id="change-1-todouble">Change 1: ToDouble</h3>

<p><strong>Commit <a href="https://chromium.googlesource.com/v8/v8/+/e0609ce60acf"><code class="language-plaintext highlighter-rouge">e0609ce6</code></a></strong>:</p>
<blockquote>
  <p>Make random ToDouble better distributed<br />
Use division instead of bit magic plus subtraction to map the RNG integer state into the [0,1) double value range.</p>
</blockquote>

<p>The old <code class="language-plaintext highlighter-rouge">ToDouble</code> produced floats in [0, 1) using “bit magic”:</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">static</span> <span class="kr">inline</span> <span class="kt">double</span> <span class="nf">ToDouble</span><span class="p">(</span><span class="kt">uint64_t</span> <span class="n">state0</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">static</span> <span class="k">const</span> <span class="kt">uint64_t</span> <span class="n">kExponentBits</span> <span class="o">=</span> <span class="kt">uint64_t</span><span class="p">{</span><span class="mh">0x3FF0000000000000</span><span class="p">};</span>
    <span class="kt">uint64_t</span> <span class="n">random</span> <span class="o">=</span> <span class="p">(</span><span class="n">state0</span> <span class="o">&gt;&gt;</span> <span class="mi">12</span><span class="p">)</span> <span class="o">|</span> <span class="n">kExponentBits</span><span class="p">;</span>
    <span class="k">return</span> <span class="n">base</span><span class="o">::</span><span class="n">bit_cast</span><span class="o">&lt;</span><span class="kt">double</span><span class="o">&gt;</span><span class="p">(</span><span class="n">random</span><span class="p">)</span> <span class="o">-</span> <span class="mi">1</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>The new version replaces this with integer division. By the way, it’s a great case to see the difference between <code class="language-plaintext highlighter-rouge">bit_cast</code> and <code class="language-plaintext highlighter-rouge">static_cast</code>:</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">static</span> <span class="kr">inline</span> <span class="kt">double</span> <span class="nf">ToDouble</span><span class="p">(</span><span class="kt">uint64_t</span> <span class="n">random</span><span class="p">)</span> <span class="p">{</span>
    <span class="c1">// Get a random [0,2**53) integer value (up to MAX_SAFE_INTEGER) by dropping</span>
    <span class="c1">// 11 bits of the state.</span>
    <span class="kt">double</span> <span class="n">random_0_to_2_53</span> <span class="o">=</span> <span class="k">static_cast</span><span class="o">&lt;</span><span class="kt">double</span><span class="o">&gt;</span><span class="p">(</span><span class="n">random</span> <span class="o">&gt;&gt;</span> <span class="mi">11</span><span class="p">);</span>
    <span class="c1">// Map this to [0,1) by division with 2**53.</span>
    <span class="k">constexpr</span> <span class="kt">double</span> <span class="n">k2_53</span><span class="p">{</span><span class="k">static_cast</span><span class="o">&lt;</span><span class="kt">uint64_t</span><span class="o">&gt;</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span> <span class="o">&lt;&lt;</span> <span class="mi">53</span><span class="p">};</span>
    <span class="k">return</span> <span class="n">random_0_to_2_53</span> <span class="o">/</span> <span class="n">k2_53</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>In the Python code it’s mirrored as:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">to_double</span><span class="p">(</span><span class="n">random</span><span class="p">):</span>
    <span class="k">return</span> <span class="p">(</span><span class="n">random</span> <span class="o">&gt;&gt;</span> <span class="mi">11</span><span class="p">)</span> <span class="o">/</span> <span class="p">(</span><span class="mi">1</span> <span class="o">&lt;&lt;</span> <span class="mi">53</span><span class="p">)</span>
</code></pre></div></div>

<p>Right-shifting by 11 bits, instead of 12, gives us one extra bit of entropy, making it 53 bits total and a statistically better, more uniform distribution.</p>

<p>This changes how the bounds are set. Recall that the observed value <code class="language-plaintext highlighter-rouge">x</code> is produced by:</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">x</span> <span class="o">=</span> <span class="nb">Math</span><span class="p">.</span><span class="nx">floor</span><span class="p">(</span><span class="nb">Math</span><span class="p">.</span><span class="nx">random</span><span class="p">()</span> <span class="o">*</span> <span class="p">(</span><span class="nx">max</span> <span class="o">-</span> <span class="nx">min</span> <span class="o">+</span> <span class="mi">1</span><span class="p">))</span> <span class="o">+</span> <span class="nx">min</span>
</code></pre></div></div>
<p>So the 53-bit integer <code class="language-plaintext highlighter-rouge">r</code> satisfies:</p>
<pre><code class="language-math">floor(r / 2^53 * (max - min + 1)) == x - min
</code></pre>
<p><code class="language-plaintext highlighter-rouge">r</code> maps to <code class="language-plaintext highlighter-rouge">x</code> when it falls in the half-open interval <code class="language-plaintext highlighter-rouge">[lo, hi)</code>, where <code class="language-plaintext highlighter-rouge">lo = normalize(x) * 2^53</code> and <code class="language-plaintext highlighter-rouge">hi = normalize(x+1) * 2^53</code>. Since <code class="language-plaintext highlighter-rouge">r</code> is an integer, the inclusive bounds are <code class="language-plaintext highlighter-rouge">[ceil(lo), ceil(hi) - 1]</code>. Using <code class="language-plaintext highlighter-rouge">floor(lo)</code> would include a value that maps to <code class="language-plaintext highlighter-rouge">x - 1</code>, so <code class="language-plaintext highlighter-rouge">ceil(lo)</code> is used. Similarly, <code class="language-plaintext highlighter-rouge">ceil(hi) - 1</code> is used because <code class="language-plaintext highlighter-rouge">ceil(hi)</code> would map to <code class="language-plaintext highlighter-rouge">x + 1</code>. In the code:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">normalize</span><span class="p">(</span><span class="n">val</span><span class="p">):</span>
    <span class="k">return</span> <span class="p">(</span><span class="n">val</span> <span class="o">-</span> <span class="nb">min</span><span class="p">)</span> <span class="o">/</span> <span class="p">(</span><span class="nb">max</span> <span class="o">-</span> <span class="nb">min</span> <span class="o">+</span> <span class="mi">1</span><span class="p">)</span>

<span class="k">def</span> <span class="nf">get_bounds</span><span class="p">(</span><span class="n">point</span><span class="p">):</span>
    <span class="n">lo</span> <span class="o">=</span> <span class="n">normalize</span><span class="p">(</span><span class="n">point</span><span class="p">)</span> <span class="o">*</span> <span class="p">(</span><span class="mi">1</span> <span class="o">&lt;&lt;</span> <span class="mi">53</span><span class="p">)</span>
    <span class="n">hi</span> <span class="o">=</span> <span class="n">normalize</span><span class="p">(</span><span class="n">point</span> <span class="o">+</span> <span class="mi">1</span><span class="p">)</span> <span class="o">*</span> <span class="p">(</span><span class="mi">1</span> <span class="o">&lt;&lt;</span> <span class="mi">53</span><span class="p">)</span>
    <span class="k">return</span> <span class="n">math</span><span class="p">.</span><span class="n">ceil</span><span class="p">(</span><span class="n">lo</span><span class="p">),</span> <span class="n">math</span><span class="p">.</span><span class="n">ceil</span><span class="p">(</span><span class="n">hi</span><span class="p">)</span> <span class="o">-</span> <span class="mi">1</span>
</code></pre></div></div>

<p>The nasty exponent edge-case check from the original, where <code class="language-plaintext highlighter-rouge">upper_exp == 1024</code>, disappears entirely.</p>

<h3 id="change-2-xorshift128-with-a-plus">Change 2: xorshift128+ with a plus</h3>

<p><strong>Commit <a href="https://chromium.googlesource.com/v8/v8/+/0596ead5b04f"><code class="language-plaintext highlighter-rouge">0596ead5</code></a></strong>:</p>
<blockquote>
  <p>Resurrect add operation in xorshift128+ implementation<br />
… which was accidentally removed in https://crrev.com/c/1238551.</p>
</blockquote>

<center>
    <img src="/assets/img/google-rng/two-milk.png" style="max-width: 400px; width: 100%;" />
    <p class="image-label">For 7 years, the Korova milk bar served plain milk instead of milk plus</p>
</center>

<p>This one is remarkable. Since 2018 <code class="language-plaintext highlighter-rouge">xorshift128+</code> wasn’t actually what it claims. It was plain <code class="language-plaintext highlighter-rouge">xorshift128</code> without the plus. The 64-bit states weren’t summed and only <code class="language-plaintext highlighter-rouge">state0</code> was used to fill the cache:</p>
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Old: void, return value discarded</span>
<span class="k">static</span> <span class="kr">inline</span> <span class="kt">void</span> <span class="nf">XorShift128</span><span class="p">(</span><span class="kt">uint64_t</span><span class="o">*</span> <span class="n">state0</span><span class="p">,</span> <span class="kt">uint64_t</span><span class="o">*</span> <span class="n">state1</span><span class="p">)</span> <span class="p">{</span>
    <span class="kt">uint64_t</span> <span class="n">s1</span> <span class="o">=</span> <span class="o">*</span><span class="n">state0</span><span class="p">;</span>
    <span class="kt">uint64_t</span> <span class="n">s0</span> <span class="o">=</span> <span class="o">*</span><span class="n">state1</span><span class="p">;</span>
    <span class="o">*</span><span class="n">state0</span> <span class="o">=</span> <span class="n">s0</span><span class="p">;</span>
    <span class="n">s1</span> <span class="o">^=</span> <span class="n">s1</span> <span class="o">&lt;&lt;</span> <span class="mi">23</span><span class="p">;</span>
    <span class="n">s1</span> <span class="o">^=</span> <span class="n">s1</span> <span class="o">&gt;&gt;</span> <span class="mi">17</span><span class="p">;</span>
    <span class="n">s1</span> <span class="o">^=</span> <span class="n">s0</span><span class="p">;</span>
    <span class="n">s1</span> <span class="o">^=</span> <span class="n">s0</span> <span class="o">&gt;&gt;</span> <span class="mi">26</span><span class="p">;</span>
    <span class="o">*</span><span class="n">state1</span> <span class="o">=</span> <span class="n">s1</span><span class="p">;</span>
<span class="p">}</span>

<span class="n">XorShift128</span><span class="p">(</span><span class="o">&amp;</span><span class="n">state</span><span class="p">.</span><span class="n">s0</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">state</span><span class="p">.</span><span class="n">s1</span><span class="p">);</span>
<span class="n">cache</span><span class="o">-&gt;</span><span class="n">set</span><span class="p">(</span><span class="n">i</span><span class="p">,</span> <span class="n">ToDouble</span><span class="p">(</span><span class="n">state</span><span class="p">.</span><span class="n">s0</span><span class="p">));</span>
</code></pre></div></div>

<p>After 7 years in production, this was fixed by returning the sum <code class="language-plaintext highlighter-rouge">s0 + s1</code>:</p>
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// New: returns s0 + s1</span>
<span class="k">static</span> <span class="kr">inline</span> <span class="kt">uint64_t</span> <span class="nf">XorShift128</span><span class="p">(</span><span class="kt">uint64_t</span><span class="o">*</span> <span class="n">state0</span><span class="p">,</span> <span class="kt">uint64_t</span><span class="o">*</span> <span class="n">state1</span><span class="p">)</span> <span class="p">{</span>
    <span class="kt">uint64_t</span> <span class="n">s1</span> <span class="o">=</span> <span class="o">*</span><span class="n">state0</span><span class="p">;</span>
    <span class="kt">uint64_t</span> <span class="n">s0</span> <span class="o">=</span> <span class="o">*</span><span class="n">state1</span><span class="p">;</span>
    <span class="o">*</span><span class="n">state0</span> <span class="o">=</span> <span class="n">s0</span><span class="p">;</span>
    <span class="n">s1</span> <span class="o">^=</span> <span class="n">s1</span> <span class="o">&lt;&lt;</span> <span class="mi">23</span><span class="p">;</span>
    <span class="n">s1</span> <span class="o">^=</span> <span class="n">s1</span> <span class="o">&gt;&gt;</span> <span class="mi">17</span><span class="p">;</span>
    <span class="n">s1</span> <span class="o">^=</span> <span class="n">s0</span><span class="p">;</span>
    <span class="n">s1</span> <span class="o">^=</span> <span class="n">s0</span> <span class="o">&gt;&gt;</span> <span class="mi">26</span><span class="p">;</span>
    <span class="o">*</span><span class="n">state1</span> <span class="o">=</span> <span class="n">s1</span><span class="p">;</span>
    <span class="k">return</span> <span class="n">s0</span> <span class="o">+</span> <span class="n">s1</span><span class="p">;</span>  <span class="c1">// &lt;-- this line was missing for 7 years</span>
<span class="p">}</span>

<span class="kt">uint64_t</span> <span class="n">random</span> <span class="o">=</span> <span class="n">XorShift128</span><span class="p">(</span><span class="o">&amp;</span><span class="n">state</span><span class="p">.</span><span class="n">s0</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">state</span><span class="p">.</span><span class="n">s1</span><span class="p">);</span>
<span class="n">cache</span><span class="o">-&gt;</span><span class="n">set</span><span class="p">(</span><span class="n">i</span><span class="p">,</span> <span class="n">ToDouble</span><span class="p">(</span><span class="n">random</span><span class="p">));</span>
</code></pre></div></div>

<p>Now the output is <code class="language-plaintext highlighter-rouge">state0 + state1</code> so the constraints become:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">points</span> <span class="o">=</span> <span class="n">points</span><span class="p">[::</span><span class="o">-</span><span class="mi">1</span><span class="p">]</span>
<span class="k">for</span> <span class="n">point</span> <span class="ow">in</span> <span class="n">points</span><span class="p">:</span>
    <span class="n">state0</span><span class="p">,</span> <span class="n">state1</span> <span class="o">=</span> <span class="n">xs128p</span><span class="p">(</span><span class="n">state0</span><span class="p">,</span> <span class="n">state1</span><span class="p">)</span>
    <span class="n">output</span> <span class="o">=</span> <span class="n">state0</span> <span class="o">+</span> <span class="n">state1</span>
    <span class="n">calc</span> <span class="o">=</span> <span class="n">z3</span><span class="p">.</span><span class="n">LShR</span><span class="p">(</span><span class="n">output</span><span class="p">,</span> <span class="mi">11</span><span class="p">)</span>

    <span class="n">lower</span><span class="p">,</span> <span class="n">upper</span> <span class="o">=</span> <span class="n">get_bounds</span><span class="p">(</span><span class="n">point</span><span class="p">)</span>
    <span class="n">solver</span><span class="p">.</span><span class="n">add</span><span class="p">(</span><span class="n">z3</span><span class="p">.</span><span class="n">And</span><span class="p">(</span><span class="n">lower</span> <span class="o">&lt;=</span> <span class="n">calc</span><span class="p">,</span> <span class="n">calc</span> <span class="o">&lt;=</span> <span class="n">upper</span><span class="p">))</span>
</code></pre></div></div>
<p>Both state variables are entangled in every constraint, which increases computation time.</p>

<p>In the prediction itself we also use the sum of the states:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">if</span> <span class="n">solver</span><span class="p">.</span><span class="n">check</span><span class="p">()</span> <span class="o">==</span> <span class="n">z3</span><span class="p">.</span><span class="n">sat</span><span class="p">:</span>
    <span class="n">model</span> <span class="o">=</span> <span class="n">solver</span><span class="p">.</span><span class="n">model</span><span class="p">()</span>
    <span class="n">state</span> <span class="o">=</span> <span class="p">{}</span>
    <span class="k">for</span> <span class="n">d</span> <span class="ow">in</span> <span class="n">model</span><span class="p">.</span><span class="n">decls</span><span class="p">():</span>
        <span class="n">state</span><span class="p">[</span><span class="n">d</span><span class="p">.</span><span class="n">name</span><span class="p">()]</span> <span class="o">=</span> <span class="n">model</span><span class="p">[</span><span class="n">d</span><span class="p">]</span>
    <span class="n">s0_val</span> <span class="o">=</span> <span class="n">state</span><span class="p">[</span><span class="s">"state0"</span><span class="p">].</span><span class="n">as_long</span><span class="p">()</span>
    <span class="n">s1_val</span> <span class="o">=</span> <span class="n">state</span><span class="p">[</span><span class="s">"state1"</span><span class="p">].</span><span class="n">as_long</span><span class="p">()</span>

    <span class="n">output</span> <span class="o">=</span> <span class="p">(</span><span class="n">s0_val</span> <span class="o">+</span> <span class="n">s1_val</span><span class="p">)</span> <span class="o">&amp;</span> <span class="mh">0xFFFFFFFFFFFFFFFF</span>
    <span class="nb">next</span> <span class="o">=</span> <span class="n">math</span><span class="p">.</span><span class="n">floor</span><span class="p">(</span><span class="n">to_double</span><span class="p">(</span><span class="n">output</span><span class="p">)</span> <span class="o">*</span> <span class="p">(</span><span class="nb">max</span> <span class="o">-</span> <span class="nb">min</span> <span class="o">+</span> <span class="mi">1</span><span class="p">))</span> <span class="o">+</span> <span class="nb">min</span>
    <span class="k">print</span><span class="p">(</span><span class="nb">next</span><span class="p">)</span>
</code></pre></div></div>
<p>Here, overflow <em>will</em> happen during summation, so a 64-bit mask (<code class="language-plaintext highlighter-rouge">0xFFFFFFFFFFFFFFFF</code>) is applied to the output.</p>

<h3 id="the-observers-paradox">The observer’s paradox</h3>
<div class="float-right-image">
    <img src="/assets/img/google-rng/milk-sipping.png" />
    <p class="image-label">Someone's sipping my entropy</p>
</div>

<p>For ease of collecting data, I logged generated points into the console and copied into my code. It worked. However, later, when I tested it again, but copied the numbers manually, the solver failed. I tried again with the data from the console: it worked. Although the numbers were the same, when I copied them manually from the widget, it failed. Weird.</p>

<p>Quantum computing? No, telemetry. The paradox was caused by a call to <code class="language-plaintext highlighter-rouge">Math.random()</code> triggered by an event listener on selection, attached to the whole document. It’s connected to Google’s telemetry and <code class="language-plaintext highlighter-rouge">Math.random()</code> is used to collect just 5% of the data.</p>

<p>Moreover, I’ve discovered that when using Chrome without a Google profile logged in, every click of the “Generate” button runs 2 extra calls of <code class="language-plaintext highlighter-rouge">Math.random()</code>. Similarly, some other clicks also trigger 2 calls. This is connected to telemetry too, which requires a 64-bit ID, generated by two halves consuming one call each.</p>

<p>Although it’s possible to handle this case in the solver by adding extra runs of xorshift for every point without any constraints, I’ve decided not to do it, due to the unclear nature of these twisted calls and my general disdain for them. They are more unpredictable than the RNG itself.</p>

<p>As a result, in my case, the solver works as-is in Google Chrome v147 with a Google profile without selecting anything on the page in between calls. Or more generally: for any sequence of consecutive <code class="language-plaintext highlighter-rouge">Math.random()</code> calls.</p>

<p>Perhaps the electron, too, behaves differently under observation because of all-encompassing telemetry calls.</p>

<p>Code: <a href="https://github.com/IvanLudvig/google-rng-hack">https://github.com/IvanLudvig/google-rng-hack</a></p>]]></content><author><name>IvanLudvig</name><email>web@ivanludvig.dev</email></author><category term="tech" /><summary type="html"><![CDATA[Predicting Math.random() in V8 engine by recovering xorshift128+ state with Z3]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://ivanludvig.dev/assets/img/google-rng/two-milk.png" /><media:content medium="image" url="https://ivanludvig.dev/assets/img/google-rng/two-milk.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Orsomarso</title><link href="https://ivanludvig.dev/travel/2026/orsomarso" rel="alternate" type="text/html" title="Orsomarso" /><published>2026-01-17T11:00:00+00:00</published><updated>2026-01-17T11:00:00+00:00</updated><id>https://ivanludvig.dev/travel/2026/orsomarso</id><content type="html" xml:base="https://ivanludvig.dev/travel/2026/orsomarso"><![CDATA[<div>
    <div id="Orsomarso-country-map-container" style="max-height: 396px; overflow: hidden; transition: max-height .25s; margin-bottom: 16px; z-index: 2; border-radius: 6px;">
        <div id="Orsomarso-country-map" style="height: 480px; max-height: 40vh; width: 100%;  z-index: 2;"></div>
    </div>

    <script>
        renderCountryMap('Orsomarso');
    </script>
</div>
<p>I arrived in Orsomarso for a day trip. It was just me and an older lady on the morning bus. The village had a wintery feel: it was quiet, chilly and smelled of chimney smoke. It’s almost 9AM. What a convenient place to put a clock, by the way.</p>
<center>
    <img src="/assets/img/travel/2026/it/orsomarso/1.jpg" />
    <p class="image-label">Clock tower above the village</p>
</center>

<p>Leaving the village for a hike, I encountered welcoming animals. The puppies were playful.</p>
<center>
    <img src="/assets/img/travel/2026/it/orsomarso/2.jpg" />
    <p class="image-label">Puppies</p>
</center>

<p>The fluffy cat looked wise and polite.</p>
<center>
    <img src="/assets/img/travel/2026/it/orsomarso/3.jpg" />
    <p class="image-label">Wise cat</p>
</center>

<p>The river is called Argentino, meaning “silvery”. I would say that the stream is clear blue.</p>
<center>
    <img src="/assets/img/travel/2026/it/orsomarso/5.jpg" />
    <p class="image-label">Clear blue stream</p>
</center>

<p>The waterfall breaks the silence. Otherwise the path is absolutely quiet, peaceful and solitary.</p>
<center>
    <img src="/assets/img/travel/2026/it/orsomarso/4.jpg" />
    <p class="image-label">Waterfall</p>
</center>

<center>
    <img src="/assets/img/travel/2026/it/orsomarso/6.jpg" />
    <p class="image-label">Shrine</p>
</center>

<p>On the way back to Orsomarso, I encountered a prominent rock. I’m sure there are legends about it, probably not good ones.</p>
<center>
    <img src="/assets/img/travel/2026/it/orsomarso/7.jpg" />
    <p class="image-label">Prominent rock</p>
</center>

<center>
    <img src="/assets/img/travel/2026/it/orsomarso/8.jpg" />
    <div class="image-margin"></div>
    <img src="/assets/img/travel/2026/it/orsomarso/9.jpg" />
    <p class="image-label">Approaching the village</p>
</center>

<p>Orsomarso has a long history with Greek heritage: the valley once had Eastern Orthodox monasteries and hermits’ caves (see <a href="https://it.wikipedia.org/wiki/Mercurion" target="_blank">Mercurion</a>). In one of the nearby caves, above the village, <a href="https://en.wikipedia.org/wiki/Nilus_the_Younger" target="_blank">Saint Nilus</a> lived as a hermit.</p>
<center>
    <img src="/assets/img/travel/2026/it/orsomarso/10.jpg" />
    <p class="image-label">View from clock tower</p>
</center>

<p>Now, a lonely statue of the Virgin Mary stands in another big cave.</p>
<center>
    <img src="/assets/img/travel/2026/it/orsomarso/11.jpg" />
    <p class="image-label">Lourdes Grotto</p>
</center>]]></content><author><name>IvanLudvig</name><email>web@ivanludvig.dev</email></author><category term="travel" /><summary type="html"><![CDATA[Peaceful waterfall hike and ancient village with Greek roots]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://ivanludvig.dev/assets/img/travel/2026/it/orsomarso/10.jpg" /><media:content medium="image" url="https://ivanludvig.dev/assets/img/travel/2026/it/orsomarso/10.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Guimarães</title><link href="https://ivanludvig.dev/travel/2025/guimaraes" rel="alternate" type="text/html" title="Guimarães" /><published>2025-12-21T11:00:00+00:00</published><updated>2025-12-21T11:00:00+00:00</updated><id>https://ivanludvig.dev/travel/2025/guimaraes</id><content type="html" xml:base="https://ivanludvig.dev/travel/2025/guimaraes"><![CDATA[<div>
    <div id="Guimaraes-country-map-container" style="max-height: 396px; overflow: hidden; transition: max-height .25s; margin-bottom: 16px; z-index: 2; border-radius: 6px;">
        <div id="Guimaraes-country-map" style="height: 480px; max-height: 40vh; width: 100%;  z-index: 2;"></div>
    </div>

    <script>
        renderCountryMap('Guimaraes');
    </script>
</div>
<p>Guimarães is called the birthplace of Portugal. I visited it to build a more personal connection to the country. I imagine right here, in this gothic canopy, the country was born. I think it cried a lot as a child.</p>
<center>
    <img src="/assets/img/travel/2025/pt/guimaraes/1.jpg" />
    <p class="image-label">Oliveira square</p>
</center>

<p>The old town is beautifully preserved and has a medieval atmosphere. Turns out, Portugal was always gloomy.</p>
<center>
    <img src="/assets/img/travel/2025/pt/guimaraes/2.jpg" />
    <p class="image-label">Old town</p>
</center>
<center>
    <img src="/assets/img/travel/2025/pt/guimaraes/3.jpg" />
    <p class="image-label">Santos Passos Church</p>
</center>
<center>
    <img src="/assets/img/travel/2025/pt/guimaraes/4.jpg" />
    <p class="image-label">Tiled picture</p>
</center>

<p>The castle somehow looks out of place: a brutal building on a green lawn. Then again, it <em>is</em> supposed to be intimidating. But there’s just something too perfect about it, that makes it seem unreal. I wish I had seen it at night too.</p>
<center>
    <img src="/assets/img/travel/2025/pt/guimaraes/5.jpg" />
    <p class="image-label">Guimarães Castle</p>
</center>

<p>It seems appropriate that Portugal grew up to build solemn monuments.</p>
<center>
    <div class="side-by-side">
        <img src="/assets/img/travel/2025/pt/guimaraes/6.jpg" />
        <img src="/assets/img/travel/2025/pt/guimaraes/7.jpg" />
    </div>
    <p class="image-label">Solemn monuments</p>
</center>

<p><a class="prev" href="/travel/2025/coimbra">
    Coimbra
</a></p>]]></content><author><name>IvanLudvig</name><email>web@ivanludvig.dev</email></author><category term="travel" /><summary type="html"><![CDATA[Guimarães is called the birthplace of Portugal. I think it cried a lot as a child.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://ivanludvig.dev/assets/img/travel/2025/pt/guimaraes/5.jpg" /><media:content medium="image" url="https://ivanludvig.dev/assets/img/travel/2025/pt/guimaraes/5.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Ethereum Blockie Search</title><link href="https://ivanludvig.dev/tech/ethereum-blockie-search" rel="alternate" type="text/html" title="Ethereum Blockie Search" /><published>2025-12-17T11:00:00+00:00</published><updated>2025-12-17T11:00:00+00:00</updated><id>https://ivanludvig.dev/tech/ethereum-blockie-search</id><content type="html" xml:base="https://ivanludvig.dev/tech/ethereum-blockie-search"><![CDATA[<style>
    .shield-container {
        display: flex;
        justify-content: center;
        align-items: center;
        gap: 16px;
        margin-bottom: 16px;
    }

    .demo-link {
        height: 20px;
        display: flex;
        gap: 4px;
        background-color: #fff;
        border: 1px solid #51942a;
        color: #51942a !important;
        text-decoration: none !important;
        padding: 2px 4px;
        font-size: 14px;
        line-height: 16px;
        box-sizing: border-box;
    }

    .demo-link img {
        height: 100%;
    }
</style>

<div class="shield-container">
    <a href="https://blockie-search.ivanludvig.dev" target="_blank" class="demo-link">
        <img src="../favicon.ico" alt="app" />
        App
    </a>
</div>

<p><img src="/assets/img/work/blockie-search.png" alt="blockie-search-demo" />
<br /></p>

<p><a href="https://blockie-search.ivanludvig.dev" target="_blank">Ethereum Blockie Search</a> is a tool to generate Ethereum addresses with specific patterns in their Blockie 8x8 identicons.</p>

<p>Developed during a small vibe-coding hackathon in Porto.</p>]]></content><author><name>IvanLudvig</name><email>web@ivanludvig.dev</email></author><category term="tech" /><summary type="html"><![CDATA[Tool to generate Ethereum addresses with specific patterns in their Blockie identicons]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://ivanludvig.dev/assets/img/work/blockie-search.png" /><media:content medium="image" url="https://ivanludvig.dev/assets/img/work/blockie-search.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Coimbra, Figueira da Foz</title><link href="https://ivanludvig.dev/travel/2025/coimbra" rel="alternate" type="text/html" title="Coimbra, Figueira da Foz" /><published>2025-12-12T11:00:00+00:00</published><updated>2025-12-12T11:00:00+00:00</updated><id>https://ivanludvig.dev/travel/2025/coimbra</id><content type="html" xml:base="https://ivanludvig.dev/travel/2025/coimbra"><![CDATA[<div>
    <div id="Coimbra-Figueira-country-map-container" style="max-height: 396px; overflow: hidden; transition: max-height .25s; margin-bottom: 16px; z-index: 2; border-radius: 6px;">
        <div id="Coimbra-Figueira-country-map" style="height: 480px; max-height: 40vh; width: 100%;  z-index: 2;"></div>
    </div>

    <script>
        renderCountryMap('Coimbra-Figueira');
    </script>
</div>
<div>
    <h3 id="Coimbra" style="margin-top: 40px; cursor: pointer;">
        Coimbra <img class="no-modal" src="/assets/marker-black.svg" style="transform: scale(0.75) translateY(-3px)" />
    </h3>
    <div id="Coimbra-map-container" style="max-height: 0; overflow: hidden; transition: max-height .25s; border-radius: 6px; z-index: 2;">
        <div id="Coimbra-map" style="height: 480px; max-height: 40vh; width: 100%;  margin-bottom: 16px; z-index: 2;"></div>
    </div>

    <script>
        document.getElementById('Coimbra').onclick = () => {
            const container = document.getElementById('Coimbra-map-container');
            const height = container.style['max-height'];
            if (height === '0px') {
                container.style['max-height'] = '496px';
                renderCityMap('Coimbra');
            } else {
                container.style['max-height'] = '0px';
            }
        }
    </script>
</div>
<p>Coimbra is monumental and classy.</p>
<center>
    <img src="/assets/img/travel/2025/pt/coimbra/1.jpg" />
    <div class="image-margin"></div>
    <img src="/assets/img/travel/2025/pt/coimbra/2.jpg" />
    <p class="image-label">City above the river</p>
</center>

<p>I wonder what the river is called. Oh, maybe Torga? I actually believed this, before looking it up: it’s Mondego. Torga is a writer. Either way, the grass makes the sign blend in nicely.</p>
<center>
    <img src="/assets/img/travel/2025/pt/coimbra/6.jpg" />
    <p class="image-label">Torga sign</p>
</center>

<p>The statue also has a label: King João III. Seems like it needs cleaning though.</p>
<center>
    <img src="/assets/img/travel/2025/pt/coimbra/3.jpg" />
    <p class="image-label">Statue of King João III</p>
</center>

<p>The railway infrastructure in Portugal has a nice minimal design with labels against a plain white background.</p>
<center>
    <img src="/assets/img/travel/2025/pt/coimbra/4.jpg" />
    <p class="image-label">Train</p>
</center>

<p>Looking at the post again, I realised that it doesn’t capture Coimbra, but only a variety of labelled objects. Sometimes even my blog fails to deliver, so as a consolation here’s a fabulous key shop sign.</p>
<center>
    <img src="/assets/img/travel/2025/pt/coimbra/5.jpg" />
    <p class="image-label">Key shop</p>
</center>
<div>
    <h3 id="Figueira-da-Foz" style="margin-top: 40px; cursor: pointer;">
        Figueira da Foz <img class="no-modal" src="/assets/marker-black.svg" style="transform: scale(0.75) translateY(-3px)" />
    </h3>
    <div id="Figueira-da-Foz-map-container" style="max-height: 0; overflow: hidden; transition: max-height .25s; border-radius: 6px; z-index: 2;">
        <div id="Figueira-da-Foz-map" style="height: 480px; max-height: 40vh; width: 100%;  margin-bottom: 16px; z-index: 2;"></div>
    </div>

    <script>
        document.getElementById('Figueira-da-Foz').onclick = () => {
            const container = document.getElementById('Figueira-da-Foz-map-container');
            const height = container.style['max-height'];
            if (height === '0px') {
                container.style['max-height'] = '496px';
                renderCityMap('Figueira-da-Foz');
            } else {
                container.style['max-height'] = '0px';
            }
        }
    </script>
</div>
<p>Figueira da Foz is a dreamy slow place. A fading resort town out of season.</p>
<center>
    <img src="/assets/img/travel/2025/pt/figueira/1.jpg" />
    <p class="image-label">Dreamy town</p>
</center>

<center>
    <img src="/assets/img/travel/2025/pt/figueira/2.jpg" />
    <p class="image-label">Seafront</p>
</center>

<p>The beach is big in both dimensions so it takes a long time to walk to the ocean.</p>
<center>
    <img src="/assets/img/travel/2025/pt/figueira/3.jpg" />
    <div class="image-margin"></div>
    <img src="/assets/img/travel/2025/pt/figueira/4.jpg" />
    <p class="image-label">Ocean</p>
</center>

<p>Figueira has an interesting example of the Portuguese pavement with a jagged pattern, which I haven’t seen before. It somehow fits this place.</p>
<center>
    <img src="/assets/img/travel/2025/pt/figueira/8.jpg" />
    <p class="image-label">Portuguese pavement</p>
</center>

<p>This pale white sunset with a hint of pink reminded me that it’s actually winter. Soft cold colours, gloomy light, trees without leaves. It feels like life has paused. It’s not sad. There are birds. Birds are always happy.</p>
<center>
    <img src="/assets/img/travel/2025/pt/figueira/5.jpg" />
    <p class="image-label">Winter sunset</p>
</center>

<p>This post captures what Figueira actually is. Still, I want to include a shopfront, even two.</p>
<center>
    <img src="/assets/img/travel/2025/pt/figueira/6.jpg" />
    <div class="image-margin"></div>
    <img src="/assets/img/travel/2025/pt/figueira/7.jpg" />
    <p class="image-label">Shopfronts</p>
</center>

<p>As we all know, everything ends in darkness. Might as well light up the Christmas tree. It’s December.</p>
<center>
    <img src="/assets/img/travel/2025/pt/figueira/10.jpg" />
    <div class="image-margin"></div>
    <img src="/assets/img/travel/2025/pt/figueira/9.jpg" />
    <p class="image-label">Night</p>
</center>

<p><a class="prev" href="/travel/2025/porto">
    Porto
</a></p>

<p><a class="next" href="/travel/2025/guimaraes">
    Guimarães
</a></p>]]></content><author><name>IvanLudvig</name><email>web@ivanludvig.dev</email></author><category term="travel" /><summary type="html"><![CDATA[Winter isn't sad. There are birds.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://ivanludvig.dev/assets/img/travel/2025/pt/figueira/4.jpg" /><media:content medium="image" url="https://ivanludvig.dev/assets/img/travel/2025/pt/figueira/4.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Porto</title><link href="https://ivanludvig.dev/travel/2025/porto" rel="alternate" type="text/html" title="Porto" /><published>2025-12-02T11:00:00+00:00</published><updated>2025-12-02T11:00:00+00:00</updated><id>https://ivanludvig.dev/travel/2025/porto</id><content type="html" xml:base="https://ivanludvig.dev/travel/2025/porto"><![CDATA[<div>
    <div id="Porto-country-map-container" style="max-height: 396px; overflow: hidden; transition: max-height .25s; margin-bottom: 16px; z-index: 2; border-radius: 6px;">
        <div id="Porto-country-map" style="height: 480px; max-height: 40vh; width: 100%;  z-index: 2;"></div>
    </div>

    <script>
        renderCountryMap('Porto');
    </script>
</div>
<p>When people asked me why I came to Porto, I told them that I didn’t want to come specifically to Porto, but my flight was here and I didn’t mind staying.</p>

<p>I’ve been here before, <a href="/travel/2024/porto">last year</a>. This time, I spent a couple of weeks in the city.</p>

<p>Have you visited Porto? Why?</p>

<center>
    <img src="/assets/img/travel/2025/pt/porto/1.jpg" />
    <div class="image-margin"></div>
    <img src="/assets/img/travel/2025/pt/porto/2.jpg" />
    <p class="image-label"></p>
</center>

<p><a class="prev" href="/travel/2025/sao-miguel">
    Sao Miguel
</a></p>

<p><a class="next" href="/travel/2025/coimbra">
    Coimbra
</a></p>]]></content><author><name>IvanLudvig</name><email>web@ivanludvig.dev</email></author><category term="travel" /><summary type="html"><![CDATA[Having nowhere else to be, I spent a couple of weeks in Porto]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://ivanludvig.dev/assets/img/travel/2025/pt/porto/1.jpg" /><media:content medium="image" url="https://ivanludvig.dev/assets/img/travel/2025/pt/porto/1.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Sao Miguel</title><link href="https://ivanludvig.dev/travel/2025/sao-miguel" rel="alternate" type="text/html" title="Sao Miguel" /><published>2025-11-25T11:00:00+00:00</published><updated>2025-11-25T11:00:00+00:00</updated><id>https://ivanludvig.dev/travel/2025/sao-miguel</id><content type="html" xml:base="https://ivanludvig.dev/travel/2025/sao-miguel"><![CDATA[<div>
    <div id="Sao-Miguel-country-map-container" style="max-height: 396px; overflow: hidden; transition: max-height .25s; margin-bottom: 16px; z-index: 2; border-radius: 6px;">
        <div id="Sao-Miguel-country-map" style="height: 480px; max-height: 40vh; width: 100%;  z-index: 2;"></div>
    </div>

    <script>
        renderCountryMap('Sao-Miguel');
    </script>
</div>
<p>In my hostel in Ponta Delgada there was a hand-written guidebook of the island. I encountered it on the same day, but 7 years later and sat on the same spot as illustrated. I love things like this.</p>
<center>
    <img src="/assets/img/travel/2025/pt/sao-miguel/1.jpg" />
    <div class="image-margin"></div>
</center>

<center>
    <img src="/assets/img/travel/2025/pt/sao-miguel/2.jpg" />
    <p class="image-label">Hand-written guidebook</p>
</center>
<div>
    <h3 id="Ponta-Delgada" style="margin-top: 40px; cursor: pointer;">
        Ponta Delgada <img class="no-modal" src="/assets/marker-black.svg" style="transform: scale(0.75) translateY(-3px)" />
    </h3>
    <div id="Ponta-Delgada-map-container" style="max-height: 0; overflow: hidden; transition: max-height .25s; border-radius: 6px; z-index: 2;">
        <div id="Ponta-Delgada-map" style="height: 480px; max-height: 40vh; width: 100%;  margin-bottom: 16px; z-index: 2;"></div>
    </div>

    <script>
        document.getElementById('Ponta-Delgada').onclick = () => {
            const container = document.getElementById('Ponta-Delgada-map-container');
            const height = container.style['max-height'];
            if (height === '0px') {
                container.style['max-height'] = '496px';
                renderCityMap('Ponta-Delgada');
            } else {
                container.style['max-height'] = '0px';
            }
        }
    </script>
</div>
<p>On a sunny day, Ponta Delgada has a lot of contrast: the buildings and pavement tiles are black and white, made from dark black volcanic rock.</p>
<center>
    <img src="/assets/img/travel/2025/pt/sao-miguel/ponta-delgada/1.jpg" />
    <p class="image-label">City gates</p>
</center>

<center>
    <img src="/assets/img/travel/2025/pt/sao-miguel/ponta-delgada/2.jpg" />
    <p class="image-label">Igreja do Colégio dos Jesuítas</p>
</center>

<center>
    <img src="/assets/img/travel/2025/pt/sao-miguel/ponta-delgada/4.jpg" />
    <p class="image-label">Peaceful morning</p>
</center>

<center>
    <img src="/assets/img/travel/2025/pt/sao-miguel/ponta-delgada/3.jpg" />
    <p class="image-label">Cute church</p>
</center>

<p>I was impressed by the Antonio Borges Botanical Garden with birds and tropical-looking palm trees, which was free to visit. It’s like a piece of jungle in the city.</p>
<center>
    <img src="/assets/img/travel/2025/pt/sao-miguel/ponta-delgada/10.jpg" />
    <p class="image-label">Botanical garden</p>
</center>

<p>I found it unexpected how the city was run down outside of the immediate centre. The narrow streets are uncomfortable for both people and cars. There are a lot of dilapidated buildings.</p>
<center>
    <div class="side-by-side">
        <img src="/assets/img/travel/2025/pt/sao-miguel/ponta-delgada/9c.jpg" />
        <img src="/assets/img/travel/2025/pt/sao-miguel/ponta-delgada/5c.jpg" />
    </div>
    <div class="image-margin"></div>
</center>

<center>
    <img src="/assets/img/travel/2025/pt/sao-miguel/ponta-delgada/8.jpg" />
    <p class="image-label">Streets</p>
</center>

<p>Like with the Canary islands in Spain, I was expecting the cities in the Azores to be nicer than mainland Portugal, but found it to be the opposite case. They seem like a sad place to be.</p>
<center>
    <img src="/assets/img/travel/2025/pt/sao-miguel/ponta-delgada/7.jpg" />
    <p class="image-label">Favela vibe</p>
</center>
<div>
    <h3 id="Sete-Cidades" style="margin-top: 40px; cursor: pointer;">
        Sete Cidades <img class="no-modal" src="/assets/marker-black.svg" style="transform: scale(0.75) translateY(-3px)" />
    </h3>
    <div id="Sete-Cidades-map-container" style="max-height: 0; overflow: hidden; transition: max-height .25s; border-radius: 6px; z-index: 2;">
        <div id="Sete-Cidades-map" style="height: 480px; max-height: 40vh; width: 100%;  margin-bottom: 16px; z-index: 2;"></div>
    </div>

    <script>
        document.getElementById('Sete-Cidades').onclick = () => {
            const container = document.getElementById('Sete-Cidades-map-container');
            const height = container.style['max-height'];
            if (height === '0px') {
                container.style['max-height'] = '496px';
                renderCityMap('Sete-Cidades');
            } else {
                container.style['max-height'] = '0px';
            }
        }
    </script>
</div>
<p>Being without a car, I decided to join a tour to Sete Cidades and Mosteiros, offered by my hostel for 30 euros. Funny thing: the tour required hiking shoes but a girl did it in flip flops. I had no hiking shoes either.</p>

<p>Even though it’s sunny on the coast, it’s often foggy inland of the island. The path with one of the most iconic views was surrounded by thick fog. There’s supposed to be a lake down there, or even two.</p>
<center>
    <img src="/assets/img/travel/2025/pt/sao-miguel/sete-cidades/2.jpg" />
    <p class="image-label">Path</p>
</center>

<p>Luckily, the lakes were visible from a different viewpoint and there are in fact two, of different colours. You can also see the ocean behind the hill. Incredible.</p>
<center>
    <img src="/assets/img/travel/2025/pt/sao-miguel/sete-cidades/1.jpg" />
    <p class="image-label">Lakes</p>
</center>

<p>The misty roads around the lakes are like a fairytale.</p>
<center>
    <img src="/assets/img/travel/2025/pt/sao-miguel/sete-cidades/3.jpg" />
    <div class="image-margin"></div>
</center>
<center>
    <img src="/assets/img/travel/2025/pt/sao-miguel/sete-cidades/4.jpg" />
    <div class="image-margin"></div>
</center>
<center>
    <img src="/assets/img/travel/2025/pt/sao-miguel/sete-cidades/5.jpg" />
    <p class="image-label">Misty roads</p>
</center>

<p>There is a big abandoned hotel nearby. Empty in a remote place on a remote island.</p>
<center>
    <img src="/assets/img/travel/2025/pt/sao-miguel/sete-cidades/6.jpg" />
    <div class="image-margin"></div>
</center>
<center>
    <img src="/assets/img/travel/2025/pt/sao-miguel/sete-cidades/7.jpg" />
    <p class="image-label">Abandoned hotel</p>
</center>

<p>The town on the lake is quiet and grey, existing in a dreamy state. Nothing bad could ever happen here, surely.</p>
<center>
    <img src="/assets/img/travel/2025/pt/sao-miguel/sete-cidades/8.jpg" />
    <p class="image-label">Church</p>
</center>
<center>
    <img src="/assets/img/travel/2025/pt/sao-miguel/sete-cidades/9.jpg" />
    <p class="image-label">Stained house</p>
</center>

<center>
    <img src="/assets/img/travel/2025/pt/sao-miguel/sete-cidades/10.jpg" />
    <p class="image-label">Lake</p>
</center>
<div>
    <h3 id="Mosteiros" style="margin-top: 40px; cursor: pointer;">
        Mosteiros <img class="no-modal" src="/assets/marker-black.svg" style="transform: scale(0.75) translateY(-3px)" />
    </h3>
    <div id="Mosteiros-map-container" style="max-height: 0; overflow: hidden; transition: max-height .25s; border-radius: 6px; z-index: 2;">
        <div id="Mosteiros-map" style="height: 480px; max-height: 40vh; width: 100%;  margin-bottom: 16px; z-index: 2;"></div>
    </div>

    <script>
        document.getElementById('Mosteiros').onclick = () => {
            const container = document.getElementById('Mosteiros-map-container');
            const height = container.style['max-height'];
            if (height === '0px') {
                container.style['max-height'] = '496px';
                renderCityMap('Mosteiros');
            } else {
                container.style['max-height'] = '0px';
            }
        }
    </script>
</div>
<p>Maybe the saying that goes “the grass is always greener…” actually refers to the isolated Azores. The grass is the greenest thing I’ve seen. Imagine how lucky are the cows. Imagine the milk.</p>
<center>
    <img src="/assets/img/travel/2025/pt/sao-miguel/mosteiros/2.jpg" />
    <div class="image-margin"></div>
</center>

<center>
    <img src="/assets/img/travel/2025/pt/sao-miguel/mosteiros/1.jpg" />
    <p class="image-label">Cows grazing on greenest grass</p>
</center>

<p>The parts of land that are formed by lava flows onto the coast have a name here: <a href="https://en.wikipedia.org/wiki/Fajã" target="_blank">Fajã</a>.</p>
<center>
    <img src="/assets/img/travel/2025/pt/sao-miguel/mosteiros/3.jpg" />
    <p class="image-label">Cliffs</p>
</center>

<p>After a busy day we swam at the beach and had beer. Actually, it was only me swimming and nobody else. The beach is on the West of the island and I imagined how small and isolated are the islands, and how if I were to get carried away by the waves, the next big piece of land was going to be North America far, far away.</p>
<center>
    <img src="/assets/img/travel/2025/pt/sao-miguel/mosteiros/4.jpg" />
    <p class="image-label">Mosteiros beach</p>
</center>
<div>
    <h3 id="Vila-Franca" style="margin-top: 40px; cursor: pointer;">
        Vila Franca do Campo <img class="no-modal" src="/assets/marker-black.svg" style="transform: scale(0.75) translateY(-3px)" />
    </h3>
    <div id="Vila-Franca-map-container" style="max-height: 0; overflow: hidden; transition: max-height .25s; border-radius: 6px; z-index: 2;">
        <div id="Vila-Franca-map" style="height: 480px; max-height: 40vh; width: 100%;  margin-bottom: 16px; z-index: 2;"></div>
    </div>

    <script>
        document.getElementById('Vila-Franca').onclick = () => {
            const container = document.getElementById('Vila-Franca-map-container');
            const height = container.style['max-height'];
            if (height === '0px') {
                container.style['max-height'] = '496px';
                renderCityMap('Vila-Franca');
            } else {
                container.style['max-height'] = '0px';
            }
        }
    </script>
</div>
<p>I love the dark churches made from volcanic rock. They look ominous. The cross has an “S.P.Q.R.” inscription. I wonder what meaning was put into it given the location.</p>
<center>
    <img src="/assets/img/travel/2025/pt/sao-miguel/vila-franca/1.jpg" />
    <p class="image-label">Igreja Matriz de São Miguel Arcanjo</p>
</center>

<center>
    <img src="/assets/img/travel/2025/pt/sao-miguel/vila-franca/9.jpg" />
    <p class="image-label">Pharmacy</p>
</center>

<p>Up on the hill, above the town, is the sanctuary of Nossa Senhora da Paz.</p>
<center>
    <img src="/assets/img/travel/2025/pt/sao-miguel/vila-franca/3.jpg" />
    <div class="image-margin"></div>

</center>
<center>
    <img src="/assets/img/travel/2025/pt/sao-miguel/vila-franca/2.jpg" />
    <div class="image-margin"></div>
</center>

<center>
    <img src="/assets/img/travel/2025/pt/sao-miguel/vila-franca/4.jpg" />
    <p class="image-label">On the way</p>
</center>

<p>The church’s cascade reminds me of <a href="/travel/2024/braga#bom-jesus" target="_blank">the Bom Jesus Sanctuary in Braga</a>, but smaller and lonelier. Actually, the whole island is lonely.</p>
<center>
    <img src="/assets/img/travel/2025/pt/sao-miguel/vila-franca/5.jpg" />
    <p class="image-label">Sanctuary of Nossa Senhora da Paz</p>
</center>

<center>
    <img src="/assets/img/travel/2025/pt/sao-miguel/vila-franca/6.jpg" />
    <p class="image-label">The legend</p>
</center>

<p>The views from the top are fantastic. I love the black volcanic cross.</p>
<center>
    <img src="/assets/img/travel/2025/pt/sao-miguel/vila-franca/8.jpg" />
    <div class="image-margin"></div>
</center>

<center>
    <img src="/assets/img/travel/2025/pt/sao-miguel/vila-franca/7.jpg" />
    <p class="image-label">View from the top</p>
</center>

<p>On another day, I made a small hike nearby around a waterfall, named Cascata do Segredo, which translates as “the secret waterfall”. I found the name to be very fitting. In fact, having discovered just about any waterfall, I would’ve been inclined to call it that way. Waterfalls are secret, intimate places.</p>

<p>I enjoyed the lush green forest, encountered unusual plants and swam in the cool water. It felt very fresh and enriched my understanding of the island’s nature, which I still didn’t fully grasp. It’s both tropical and northern.</p>
<center>
    <img src="/assets/img/travel/2025/pt/sao-miguel/vila-franca/10.jpg" />
    <img src="/assets/img/travel/2025/pt/sao-miguel/vila-franca/12.jpg" />
    <img src="/assets/img/travel/2025/pt/sao-miguel/vila-franca/11.jpg" />
    <p class="image-label">Cascata do Segredo hike</p>
</center>

<p>The trail passed through remains of an abandoned hydroelectric station, but I didn’t care for it at all. I never understood what draws people to abandoned structures unless they’re very old.</p>
<center>
    <img src="/assets/img/travel/2025/pt/sao-miguel/vila-franca/15.jpg" />
    <p class="image-label">Abandoned hydroelectric station</p>
</center>

<center>
    <div class="side-by-side">
        <img src="/assets/img/travel/2025/pt/sao-miguel/vila-franca/14.jpg" />
        <img src="/assets/img/travel/2025/pt/sao-miguel/vila-franca/13.jpg" />
    </div>
    <p class="image-label">Village</p>
</center>
<div>
    <h3 id="Furnas" style="margin-top: 40px; cursor: pointer;">
        Furnas <img class="no-modal" src="/assets/marker-black.svg" style="transform: scale(0.75) translateY(-3px)" />
    </h3>
    <div id="Furnas-map-container" style="max-height: 0; overflow: hidden; transition: max-height .25s; border-radius: 6px; z-index: 2;">
        <div id="Furnas-map" style="height: 480px; max-height: 40vh; width: 100%;  margin-bottom: 16px; z-index: 2;"></div>
    </div>

    <script>
        document.getElementById('Furnas').onclick = () => {
            const container = document.getElementById('Furnas-map-container');
            const height = container.style['max-height'];
            if (height === '0px') {
                container.style['max-height'] = '496px';
                renderCityMap('Furnas');
            } else {
                container.style['max-height'] = '0px';
            }
        }
    </script>
</div>
<p>Furnas is full of volcanic hot springs, which makes the whole town smell particular. In the Caldeiras park you could also see and hear them. There was a nativity scene arranged around the springs, right in the middle of the steam.</p>
<center>
    <img src="/assets/img/travel/2025/pt/sao-miguel/furnas/1.jpg" />
    <p class="image-label">Caldeiras park</p>
</center>

<p>At the water fountains you can taste them. The water has a pronounced metallic taste and slightly carbonated, feeling electric. Interestingly, the fountains are labeled with exact temperature. Some of them are very hot.</p>
<center>
    <img src="/assets/img/travel/2025/pt/sao-miguel/furnas/2.jpg" />
    <p class="image-label">Water fountain</p>
</center>

<p>Finally, the ultimate experience is bathing in the springs. I visited the smaller Poça da Dona Beija thermal park. It was warm and cozy especially with the colder weather. I also loved greenery around and recall the distinct colour combination of red iron and all sorts of green plants.</p>
<center>
    <div class="side-by-side">
        <img src="/assets/img/travel/2025/pt/sao-miguel/furnas/6.jpg" />
        <img src="/assets/img/travel/2025/pt/sao-miguel/furnas/5.jpg" />
    </div>
    <p class="image-label">Poça da Dona Beija thermal park</p>
</center>

<p>Furnas has hints of green in the town structures too.</p>
<center>
    <img src="/assets/img/travel/2025/pt/sao-miguel/furnas/3.jpg" />
    <div class="image-margin"></div>
</center>

<center>
    <img src="/assets/img/travel/2025/pt/sao-miguel/furnas/4.jpg" />
    <p class="image-label">Green tint</p>
</center>

<p>Finally, I walked to the lake. It was the first time I walked through a bamboo forest. I also loved the solemn grey gothic church.</p>
<center>
    <img src="/assets/img/travel/2025/pt/sao-miguel/furnas/7.jpg" />
    <p class="image-label">Furnas lake</p>
</center>

<p>After visiting the lake, I hitchhiked back to Ponta Delgada. I waited 40min for a ride and joined the driver on a side quest to get a ridiculously large amount of water from one of the fountains. I filled my modest water bottle too. This took quite some time, so I admired the sunset out of the car window. This was my last night on Sao Miguel.</p>
<center>
    <div class="side-by-side">
        <img src="/assets/img/travel/2025/pt/sao-miguel/furnas/8.jpg" />
        <img src="/assets/img/travel/2025/pt/sao-miguel/furnas/9.jpg" />
    </div>
    <p class="image-label">Hitchhiking</p>
</center>

<p>After a week, I left the isolated green island. Lush and alive but alone in the middle of the ocean.</p>

<p><a class="prev" href="/travel/2025/gijon">
    Gijón
</a></p>

<p><a class="next" href="/travel/2025/porto">
    Porto
</a></p>]]></content><author><name>IvanLudvig</name><email>web@ivanludvig.dev</email></author><category term="travel" /><summary type="html"><![CDATA[Lush and alive but alone in the middle of the ocean]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://ivanludvig.dev/assets/img/travel/2025/pt/sao-miguel/sete-cidades/1.jpg" /><media:content medium="image" url="https://ivanludvig.dev/assets/img/travel/2025/pt/sao-miguel/sete-cidades/1.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Gijón</title><link href="https://ivanludvig.dev/travel/2025/gijon" rel="alternate" type="text/html" title="Gijón" /><published>2025-11-19T11:00:00+00:00</published><updated>2025-11-19T11:00:00+00:00</updated><id>https://ivanludvig.dev/travel/2025/gijon</id><content type="html" xml:base="https://ivanludvig.dev/travel/2025/gijon"><![CDATA[<div>
    <div id="Gijon-country-map-container" style="max-height: 396px; overflow: hidden; transition: max-height .25s; margin-bottom: 16px; z-index: 2; border-radius: 6px;">
        <div id="Gijon-country-map" style="height: 480px; max-height: 40vh; width: 100%;  z-index: 2;"></div>
    </div>

    <script>
        renderCountryMap('Gijon');
    </script>
</div>
<p>Gijón is a coastal city just 30km from the capital of Asturias, Oviedo. They are of comparable sizes and I chose to stay in Gijón because I prefer the sea. The city sits against green hills and snowy mountains further away, those are part of Picos de Europa.</p>
<center>
    <img src="/assets/img/travel/2025/es/gijon/0.jpg" />
    <p class="image-label">City against the mountains</p>
</center>

<p>My BlaBlaCar driver wondered why I’d come here and said the city is industrial and not nice. At least I was reassured about my Spanish skills because our long conversation was in Spanish.</p>

<p>Indeed, I found Gijón quite unremarkable at first glance. The city feels provincial.</p>
<center>
    <img src="/assets/img/travel/2025/es/gijon/4.jpg" />
    <p class="image-label">Streets</p>
</center>

<p>The bus station has a slick old sign. Unfortunately, it lacks any other relevant signs.</p>
<center>
    <img src="/assets/img/travel/2025/es/gijon/7.jpg" />
    <p class="image-label">Bus station</p>
</center>

<p>A piece of street art with overlapping faces on a weathered wall touched me. I associate it with something deeply human: the quiet desperation of realizing the fragility of life.</p>
<center>
    <img src="/assets/img/travel/2025/es/gijon/5.jpg" />
    <p class="image-label">Faces on a wall</p>
</center>

<p>Windows facing the ocean form an ocean of their own, that flows from building to building.</p>
<center>
    <img src="/assets/img/travel/2025/es/gijon/8.jpg" />
    <p class="image-label">Windows</p>
</center>

<p>The ocean is fantastic and the city has a nice long beach. Even surfers come here, or at least one did when I was there.</p>
<center>
    <img src="/assets/img/travel/2025/es/gijon/1.jpg" />
    <img src="/assets/img/travel/2025/es/gijon/3.jpg" />
    <img src="/assets/img/travel/2025/es/gijon/12.jpg" />
    <p class="image-label">Ocean</p>
</center>

<p>It’s possible to walk far along the coast. Different monuments and sculptures, often modern, are placed along it.</p>
<center>
    <img src="/assets/img/travel/2025/es/gijon/9.jpg" />
    <p class="image-label">Coast</p>
</center>

<p>Such as Elogio del Horizonte. When I first encountered a similar coastal sculpture in <a href="/travel/2025/basque#Donostia" target="_blank">Donostia</a>, Comb of the Wind, I didn’t understand it. The concrete and rust, evoking a feeling of burden – why is it there? Gijón was the last city on my Northern Spain trip and I finally got it.</p>
<center>
    <img src="/assets/img/travel/2025/es/gijon/10.jpg" />
    <p class="image-label">Elogio del Horizonte</p>
</center>

<p>My head spun from it.</p>
<center>
    <img src="/assets/img/travel/2025/es/gijon/11.jpg" />
    <p class="image-label">Experiencing art</p>
</center>

<p><a class="prev" href="/travel/2025/santander">
    Santander
</a></p>

<p><a class="next" href="/travel/2025/sao-miguel">
    Sao Miguel
</a></p>]]></content><author><name>IvanLudvig</name><email>web@ivanludvig.dev</email></author><category term="travel" /><summary type="html"><![CDATA[Understanding coastal sculptures]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://ivanludvig.dev/assets/img/travel/2025/es/gijon/1.jpg" /><media:content medium="image" url="https://ivanludvig.dev/assets/img/travel/2025/es/gijon/1.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry></feed>