<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.4.1">Jekyll</generator><link href="https://agarie.net.br/feed.xml" rel="self" type="application/atom+xml" /><link href="https://agarie.net.br/" rel="alternate" type="text/html" /><updated>2026-03-25T23:24:31-03:00</updated><id>https://agarie.net.br/feed.xml</id><title type="html">Strange Aeons</title><subtitle>This is the personal website of Carlos Agarie.</subtitle><author><name>Carlos Agarie</name></author><entry><title type="html">Decision making in Pokémon Emerald’s Battle Pike</title><link href="https://agarie.net.br/2026/02/pokemon-emerald-battle-pike-decision-making.html" rel="alternate" type="text/html" title="Decision making in Pokémon Emerald’s Battle Pike" /><published>2026-02-28T00:00:00-03:00</published><updated>2026-02-28T00:00:00-03:00</updated><id>https://agarie.net.br/2026/02/pokemon-emerald-battle-pike-decision-making</id><content type="html" xml:base="https://agarie.net.br/2026/02/pokemon-emerald-battle-pike-decision-making.html"><![CDATA[<p>This post presents a “solution” to the Battle Pike in Pokémon Emerald’s Battle Frontier.
I assume you understand <em>a bit</em> about Pokémon, but the section <a href="#battle-pike-mechanics">on mechanics</a> should give you enough context to enjoy <a href="#the-decision-problem">the decision problem</a>.</p>

<h2 id="introduction">Introduction</h2>

<p>I recently replayed through the Battle Frontier in Pokémon Emerald for the <a href="https://www.reddit.com/r/pokemonribbons/wiki/index/">Ribbon Master Challenge</a>, where you take a Pokémon through <em>all</em> the games they can be transferred into and earn all the available ribbons along the way.
You could capture a Pokémon in any game from Ruby &amp; Sapphire to Scarlet &amp; Violet:
the only requirement for the title of <em>Ribbon Master</em> is said Pokémon obtaining all Ribbons (and now Marks) they can in the games it can visit.
<span class="marginnote">
    Marks are an extension of Ribbons. Most are earned randomly when a Pokémon is captured, but at least a few can be obtained by fulfilling certain conditions. See <a href="https://bulbapedia.bulbagarden.net/wiki/List_of_Ribbons_and_Marks_by_index_number_in_Generation_IX">10</a> for a list.
</span></p>

<p>This time, I decided to take my Arcanine from Pokémon XD through the challenge, and one of its first stops was the Battle Tower in the Battle Frontier.
I got through both modes (Level 50 and Open Level) with no major difficulties, but one thing I noticed is that I haven’t earned the Gold Symbols on my physical cartridge; I only played through the Frontier facilities on an emulator.
So I took a detour to make this right.</p>

<p><span class="marginnote">
    <img src="/images/battle-pike-outside.png" alt="Screenshot of Pokémon Emerald showing outside of the Battle Pike in the Battle Frontier." />
</span>
After losing 2 runs to bad luck and no planning in the Battle Pike, I started searching for suggestions on how to approach it.
I found <a href="https://www.reddit.com/r/PokemonEmerald/comments/15u8o9w/advanced_techniques_for_the_battle_pike/">a post on Reddit</a> with a lot of information on winning strategies, which helped me have an easier time and eventually earn the Gold Symbol, but most interestingly made me realize three things:</p>

<ol>
  <li>The state of your team affects the distribution of possible outcomes each round, i.e., what you see in each room depend on the health (also PPs used and presence of status effects) of your Pokémon.</li>
  <li>Furthermore, the <em>risk</em> of each option also depends on the state of your team and how many rounds are left in the challenge. You can take more battles if your team is healthy, or gamble for recovery if you’re almost defeated.</li>
  <li>Thus, the Battle Pike can be understood as a problem of <em>Decision Under Uncertainty</em>, with a corresponding solution in the form of a <em>Decision Table</em>.</li>
</ol>

<p>Next up I review the mechanics of the Battle Pike before discussing why it’s a problem of this sort.</p>

<h2 id="battle-pike-mechanics">Battle Pike Mechanics</h2>

<p><span class="marginnote">
    <img src="/images/battle-pike-luck.png" alt="Screenshot of Pokémon Emerald showing the receptionist at the Battle Pike welcoming the player." />
</span>
The Battle Pike is a facility located in the Battle Frontier themed around “luck”.
Each round consists of two rooms: in the first one, you choose one out of three exits, after which you are faced with a randomly selected challenge (listed below).
You win if you manage to survive 7 rounds without your whole team fainting.</p>

<p>But choosing a room at random would be boring, and that’s why an NPC stationed at each first room predicts what is behind one of the 3 exits.
She will specify a door and tell you one of 4 quotes, corresponding to 2 possible challenges.
The other unspecified doors can contain any of the other challenges.</p>

<p>The following table describes each quotes and their associated challenges:</p>

<table id="battle-pike-hints">
    <caption>
        Hints given by the Receptionist during the Battle Pike challenge.
    </caption>
    <thead>
        <tr>
            <th scope="col">Option</th>
            <th scope="col">Quote</th>
            <th scope="col">Result 1</th>
            <th scope="col">Result 2</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <th scope="row">A</th>
            <td>"For some odd reason, I felt a wave of nostalgia coming from it"</td>
            <td><em>Status Condition</em></td>
            <td><em>One or two recovery</em></td>
        </tr>
        <tr>
            <th scope="row">B</th>
            <td>"Is it...A Trainer? I sense the presence of people"</td>
            <td><em>Single Battle</em></td>
            <td><em>Full Recovery</em></td>
        </tr>
        <tr>
            <th scope="row">C</th>
            <td>"It seems to have the distinct aroma of Pokémon wafting around it"</td>
            <td><em>Wild Pokémon</em></td>
            <td><em>Hard Single Battle and Recovery</em></td>
        </tr>
        <tr>
            <th scope="row">D</th>
            <td>"I seem to have heard something...It may have been whispering"</td>
            <td><em>No Event</em></td>
            <td><em>Double Battle</em></td>
        </tr>
    </tbody>
</table>

<p>In addition, the state of your team make some of the outcomes impossible.
<span class="marginnote">
    We could review the <a href="https://github.com/pret/pokeemerald">source code of the game</a> to verify if that’s really what happens. I might do that eventually.
</span>
<a href="https://www.reddit.com/r/pokemonribbons/wiki/index/">The original Reddit post</a> gives some evidence for that and my personal experience is similar.
Some of these situations are as follows:</p>

<ul>
  <li>If your whole team is afflicted by status (paralyzed, sleeping, burned, or frozen), then a Kirlia or Dusclops cannot show up. Option A will be <em>One or two recovery</em>.</li>
  <li>If your team is in perfect condition (full HP, no PPs used, no status), then there won’t be any form of recovery. So option A will be <em>Status Condition</em>, option B will be <em>Single Battle</em>, and option C will be <em>Wild Pokémon</em>.</li>
  <li>If you have only one Pokémon left, then you cannot play a double battle. Option D will be <em>No Event</em>.</li>
</ul>

<p><span class="marginnote">
    <img src="/images/battle-pike-hint-1.png" alt="Screenshot of Pokémon Emerald in the Battle Pike receiving a hint." />
</span>
<span class="marginnote">
    <img src="/images/battle-pike-hint-2.png" alt="Screenshot of Pokémon Emerald in the Battle Pike receiving a hint, continued." />
</span>
You team consists of only 3 Pokémon for the challenge and there is no auto-healing between rounds, different from facilities like the Battle Tower and the Battle Factory.
This is the reason random Status Conditions from <em>option A</em> can be a problem: some of your Pokémon will be left sleeping, paralyzed, or frozen and there’s not much you can do outside of hoping for a healing room.</p>

<p>In the <em>Wild Pokémon</em> room from <em>option C</em> you are put in a short maze and the objective is just to walk to the end facing random encounters.
It is very important to make sure the first Pokémon in your team can run from these encounters to avoid receiving status or wasting PPs unnecessarily.</p>

<p>Finally, you have the option of challenging the Battle Pike at <em>Level 50</em> or at “Open Level”, which means the opposing NPC’s Pokémon levels will be the maximum value between 60 and the highest level in your team.
If your Pokémon are at level 55, opponents will be at 60, and if your highest level is 81, opponents’ will be 81 as well.
This is mostly irrelevant for how hard the challenge is, except that at <em>Level 50</em> you won’t encounter certain Pokémon that evolve past it (e.g., Dragonite, Tyranitar).</p>

<p>That covers the mechanics. Now the question is how to win consistently.</p>

<h2 id="the-decision-problem">The Decision Problem</h2>

<p>The information you have when you enter a room is:</p>

<ul>
  <li><strong>The state of your team</strong>: are your Pokémon healthy or not? How much PP do you still have on the most important moves? Do you still have your items?</li>
  <li>How many rooms do you still have to go through?</li>
  <li>The quote from the NPC describing what’s behind one particular door.</li>
</ul>

<p>The other 2 exits are random and equivalent, so you either exit through the room the NPC just described or one of them.
Which means you have to weight in what can happen at each room by estimating how much you can afford to lose and still be able to complete the challenge.
For example, if you have a single Pokémon left and you get the quote “I seem to have heard something…It may have been whispering”, that exit will lead to <strong>No Event</strong> necessarily, as previously mentioned.</p>

<p>The decision problem, then, lies in judging the current state of your team and seeing what outcomes are possible based on the quote you get.
And choosing the ones which maximize the chance you’ll be able to survive all rounds.</p>

<p>Suppose you start a new challenge. Thus, your team is <strong>fully healed</strong>.
The NPC gives you the following hint: “Is it…A Trainer? I sense the presence of people” about the room in the right.
What can be inferred from this situation?</p>

<p>That means this room either has a <em>Single Battle</em> or <em>Full Recovery</em>.
However, you can’t get a recovery room if your team is fully healed, so you can conclude there’s going to be a <em>Single Battle</em> behind that door.</p>

<p>The other two rooms represent the 6 alternative paths, corresponding to the outcomes from options A, C, and D, due to option B being the one mentioned in the quote.
What can happen if you take them are the following:</p>

<ul>
  <li>Option A (either Status Condition or One or two recovery): you can’t get recovery, so it’s guaranteed Status Condition. Bad!</li>
  <li>Option C (either Wild Pokémon or Hard Single Battle and Recovery): you can’t get recovery, so it’s guaranteed Wild Pokémon. But you made sure (right?) that your first Pokémon is able to run away from these encounters, so this is a free room. Good!</li>
  <li>Option D (either No Event or Double Battle): both can happen, but Double Battles tend to be relatively simple most of the time, and No Event is obviously great. Good!</li>
</ul>

<p>That means 2 out of 3 possible scenarios for the rooms not mentioned in the quote are Good and you should take them (which one doesn’t matter).
You can still get unlucky and get Status Condition (⅓) or a Double Battle (⅙), but it’s better than a guaranteed Single Battle (which we already defined as <em>generally harder</em> than Double Battles).</p>

<p>Now let’s suppose you are at the last room before the challenge ends and it’s not a battle against Pike Queen Lucy.
<span class="marginnote">
You can only face Pike Queen Lucky after round 2 (14 rooms cleared) and round 10 (70 rooms cleared).
</span>
The NPC gives you the quote: “For some odd reason, I felt a wave of nostalgia coming from it” on the central path.
In this case, you should always go for that door: you either get <em>recovery</em> or <em>status condition</em>, which is not a problem due to this being the last room.</p>

<p>Here are the heuristics described in <a href="https://www.reddit.com/r/PokemonEmerald/comments/15u8o9w/advanced_techniques_for_the_battle_pike/">the original Reddit post</a>, organized as a Decision Table, with additions by me.
“Lightly Damaged” means your party has almost 100% HP or used a few PPs, etc; it’s close to Full Health, but recovery rooms will show up.</p>

<table id="battle-pike-decision-table">
    <caption>
        Recommended actions for each possible state in the Battle Pike.
        &#10003; means "enter this room", &#10007; "do not enter", and &#9632; "it depends on your specific team".
    </caption>
    <thead>
        <tr>
            <th rowspan="2">Option</th>
            <th rowspan="2">Quote</th>
            <th colspan="3">State of your team</th>
        </tr>
        <tr>
            <th>Full Health</th>
            <th>Lightly Damaged</th>
            <th>Heavily Damaged</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td rowspan="2">A</td>
            <td rowspan="2">"For some odd reason, I felt a wave of nostalgia coming from it"</td>
            <td>Rooms 1&ndash;6 &#10003; &xrarr; Guaranteed status effect</td>
            <td>Rooms 1&ndash;6 &#9632; &xrarr; Gets more dangerous as you progress, with the potential for triple sleep on the sixth room</td>
            <td rowspan="2">&#10003; &xrarr; An extra status effect won't doom you much more than you already are and 1-2 healed Pokémon can save you; Guaranteed no trainer battle on room 7</td>
        </tr>
        <tr>
            <td>Room 7 &#10003; &xrarr; Guarantees no trainer battles</td>
            <td>Room 7 &#10003; &xrarr; Guarantees no trainer battles</td>
        </tr>
        <tr>
            <td>B</td>
            <td>"Is it...A Trainer? I sense the presence of people" </td>
            <td>&#10007; &xrarr; Guaranteed single battle with no recovery</td>
            <td>&#10007; &xrarr; The small heal does not justify the risk</td>
            <td>&#10007; &xrarr; The risk only makes sense if your party is so weak that the 50% chance of a full heal seems worth it</td>
        </tr>
        <tr>
            <td>C</td>
            <td>"It seems to have the distinct aroma of Pokémon wafting around it" </td>
            <td>&#10003; &xrarr; Guaranteed Wild pokémon; Use 1 PP here to enable odds of recovery rooms moving forward</td>
            <td>&#10003; &xrarr; You can run from Wild Pokémon and the chance of winning a Hard Single Battle is pretty high while lightly damaged</td>
            <td>&#10007; &xrarr; A hard single battle might end your run</td>
        </tr>
        <tr>
            <td>D</td>
            <td>"I seem to have heard something...It may have been whispering"</td>
            <td>&#10003; &xrarr; &frac12; chance of neutral/positive outcome instead of &frac14; of a neutral/positive outcome in the other rooms</td>
            <td>&#10003; &xrarr; Double battles are generally easy and there's &frac12; of No Event</td>
            <td>&#10003; &xrarr; It's guaranteed neutral if you only have 1 Pokémon; The odds are better than a regular or hard single battle otherwise</td>
        </tr>
    </tbody>
</table>

<h2 id="conclusion">Conclusion</h2>

<p>What’s charming about the Battle Pike is that it is <strong>upfront</strong> about luck: “Where the luck of TRAINERS is put to the test…”.
And while that is a frustrating aspect of playing Pokémon, it’s also so characteristic of the series that I cannot think of battling without accounting for it.
Some amount of luck is <em>required</em> to win, independent of how much preparation you got.</p>

<p>There’s probably more situations in the series where a Decision Table like the one in this post can be used to describe the solution to a particular challenge.
Unfortunately, it’s hard to justify the time investment:</p>

<ul>
  <li>Outside of <a href="https://bulbapedia.bulbagarden.net/wiki/Battle_facility">Battle Facilities</a>, the in-game challenges are fairly simple and writing a Decision Table for most of it would be a waste of time.</li>
  <li>The complexity of competitive battling makes almost any kind of theoretical treatment worthless. There’s just too much to take into consideration. I might reconsider this once I get to study Game Theory more throughly.</li>
</ul>

<p>Maybe there are other Facilities where this methodology would be useful?
I’ll keep my eyes open next time I play.</p>

<h2 id="references">References</h2>

<ol>
  <li>This Reddit thread is what inspired me to write this post. ⟶ <a href="https://www.reddit.com/r/PokemonEmerald/comments/15u8o9w/advanced_techniques_for_the_battle_pike/">Advanced Techniques for the Battle Pike (r/PokemonEmerald subreddit)</a>.</li>
  <li><a href="https://bulbapedia.bulbagarden.net/wiki/Battle_Pike">Battle Pike on Bulbapedia</a>.</li>
  <li><a href="https://bulbapedia.bulbagarden.net/wiki/Battle_Frontier_(Generation_III)">Battle Frontier on Bulbapedia</a>.</li>
  <li>The generalized term for these minigames is “Battle Facility”:
    <ul>
      <li><a href="https://bulbapedia.bulbagarden.net/wiki/Battle_facility">Battle Facility on Bulbapedia</a></li>
      <li><a href="https://discord.gg/8RMcwybe2t">Battle Facilities Discord</a></li>
    </ul>
  </li>
  <li>There are many reports from people of the Battle Facilities community listing what teams and strategies they used. It’s likely that this post could be augmented by information posted there. ⟶ <a href="https://www.smogon.com/forums/threads/gen-iii-battle-frontier-discussion-and-records.3648697/post-8080772">Battle Pike Records on Smogon</a>.</li>
  <li><a href="https://pokemonpostgame.com/src/PKMNPostGame.Web/rse_checklist.html">Pokémon Emerald Post-Game Checklist</a>.</li>
  <li>Several references on ribbons, marks, and the Ribbon Master Challenge:
    <ul>
      <li><a href="https://www.reddit.com/r/pokemonribbons/">Pokémon Ribbons subreddit</a></li>
      <li><a href="https://www.reddit.com/r/pokemonribbons/wiki/index/">What is the Ribbon Master Challenge?</a></li>
      <li><a href="https://bulbapedia.bulbagarden.net/wiki/List_of_Ribbons_and_Marks_by_index_number_in_Generation_IX">List of Ribbons and Marks by index number in Generation IX</a></li>
    </ul>
  </li>
  <li><a href="https://github.com/pret/pokeemerald">Decompilation of Pokémon Emerald</a>.</li>
</ol>]]></content><author><name>Carlos Agarie</name></author><category term="pokemon" /><category term="games" /><category term="decision theory" /><summary type="html"><![CDATA[This post presents a “solution” to the Battle Pike in Pokémon Emerald’s Battle Frontier. I assume you understand a bit about Pokémon, but the section on mechanics should give you enough context to enjoy the decision problem.]]></summary></entry><entry><title type="html">Dotted paper generator in a single HTML file</title><link href="https://agarie.net.br/2026/02/dotgrid-paper-generator-in-a-single-html-file.html" rel="alternate" type="text/html" title="Dotted paper generator in a single HTML file" /><published>2026-02-07T13:59:00-03:00</published><updated>2026-02-07T13:59:00-03:00</updated><id>https://agarie.net.br/2026/02/dotgrid-paper-generator-in-a-single-html-file</id><content type="html" xml:base="https://agarie.net.br/2026/02/dotgrid-paper-generator-in-a-single-html-file.html"><![CDATA[<p>Here’s the repository: <a href="https://github.com/agarie/dotgrid">agarie/dotgrid on GitHub</a>.</p>

<p>And here’s the app: <a href="https://agarie.github.io/dotgrid/dotgrid.html">dotted paper generator</a>.</p>

<h2 id="why-dotted-paper">Why dotted paper?</h2>

<p>I like having scratch paper while I’m at my computer for scribbling, doodling, making quick notes &amp; lists, or just sorting what I want to do in that particular day.</p>

<p>Dotted  is superior to regular ruled paper for this kind of usage. It’s mostly out of the way when one is working free form, but flexible enough that you can write as if it was lined, easier to draw with proportions, etc.</p>

<p>I have a large pile of old printed pages (300+) I use as a scratch pad and thought it would be cool to print a dot grid on them instead of just using it as plain paper.
So I began searching for a free generator online and found <a href="https://v.cx/2024/10/dotgrid">Rob Shearer’s webapp</a> after stumbling upon some of the problems he mentioned in his post, like generators which left annoying watermarks on the created PDF.</p>

<p>I used that app for a few months until I noticed I kept (manually) adding a vertical line splitting the page into more or less 70% / 30% sections.
Writing and longer lists on the left side, short reminders and minimal lists on the right one.
It’s not perfect, but it works well and is similar to how I’ve organized my study notes for almost a decade now.</p>

<h2 id="modifications">Modifications</h2>

<p>So I cloned the project and made the following changes to it:</p>

<ol>
  <li>I added an option to create a vertical line on top of one of the dotted columns. The input takes a percentage, which is translated to the nearest column. I suspect this might be a problem in tighter grids, but I couldn’t find any problems after a few minutes of superficial manual testing.</li>
  <li>I like to write page numbers on the top right because I archive these sheets after using them. (I don’t have a strong reason for it.) So I added another option to add a field on the top right for a page number. I didn’t adapt it to mirrored pages, so it does look weird. I might fix this someday.</li>
  <li>I updated the default values on some of the inputs so it’s closer to what I like to use.</li>
  <li>I also added a tiny bit of basic CSS and updated the HTML so it uses a form. Given this is a small, single-page app, etc, this is probably unnecessary, but I like how it looks now.</li>
</ol>

<p>I should prepare a pull request with the HTML and CSS changes I made to thank Rob for his work, too, someday.</p>]]></content><author><name>Carlos Agarie</name></author><summary type="html"><![CDATA[Here’s the repository: agarie/dotgrid on GitHub.]]></summary></entry><entry><title type="html">A PNG showing differently in Firefox and Chrome</title><link href="https://agarie.net.br/2014/02/a-png-showing-differently-in-firefox-and-chrome.html" rel="alternate" type="text/html" title="A PNG showing differently in Firefox and Chrome" /><published>2014-02-07T00:00:00-02:00</published><updated>2014-02-07T00:00:00-02:00</updated><id>https://agarie.net.br/2014/02/a-png-showing-differently-in-firefox-and-chrome</id><content type="html" xml:base="https://agarie.net.br/2014/02/a-png-showing-differently-in-firefox-and-chrome.html"><![CDATA[<p><strong>Update (2026-02)</strong>: The support for APNGs <a href="https://caniuse.com/apng">has improved considerably</a> since this post was written.
It was still an interesting investigation into PNGs so I’m going to leave it here.</p>

<p>I was chatting with some friends on IM when someone posted a link to a Psyduck.
The image had the following instructions: “Now open this in Firefox”.
And so I did, and it now said “Now open this in IE (or Chrome/Safari)”, presented with a different background color.</p>

<figure>
  <img src="/images/psyduck.png" alt=" A Psyduck image that supposedly looks different depending on your browser" />
  
</figure>

<p><em>What</em>.</p>

<p>Pattern matching against the User-Agent of the HTTP request to send two different files was my first guess.
However, the PNGs downloaded through each browser were identical.
Worse: the same behavior was present with the file copied locally, so it should be something with the image itself.</p>

<p>Since I never read about image file formats in depth, I had no clue what could be going on.
So after finding out about <a href="https://github.com/wvanbergen/chunky_png">the chunkypng gem</a>, I started poking around the PNG and found out how to look at its chunks.</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">&gt;&gt;</span> <span class="nb">require</span> <span class="s1">'chunky_png'</span>
<span class="o">&gt;&gt;</span> <span class="n">q</span> <span class="o">=</span> <span class="no">ChunkyPNG</span><span class="o">::</span><span class="no">Datastream</span><span class="p">.</span><span class="nf">from_file</span> <span class="s2">"psyduck.png"</span>
<span class="o">&gt;&gt;</span> <span class="n">q</span><span class="p">.</span><span class="nf">each_chunk</span> <span class="p">{</span><span class="o">|</span><span class="n">chunk</span><span class="o">|</span> <span class="nb">p</span> <span class="n">chunk</span><span class="p">.</span><span class="nf">type</span><span class="p">}</span>
<span class="s2">"IHDR"</span>
<span class="s2">"acTL"</span>
<span class="s2">"fcTL"</span>
<span class="s2">"fdAT"</span>
<span class="s2">"fdAT"</span>
<span class="s2">"fdAT"</span>
<span class="s2">"fdAT"</span>
<span class="s2">"fdAT"</span>
<span class="s2">"fdAT"</span>
<span class="s2">"fdAT"</span>
<span class="s2">"fdAT"</span>
<span class="s2">"IDAT"</span>
<span class="s2">"IDAT"</span>
<span class="s2">"IDAT"</span>
<span class="s2">"IDAT"</span>
<span class="s2">"IDAT"</span>
<span class="s2">"IDAT"</span>
<span class="s2">"IDAT"</span>
<span class="s2">"IDAT"</span>
<span class="s2">"IEND"</span>
<span class="o">&gt;&gt;</span> <span class="n">q</span><span class="p">.</span><span class="nf">each_chunk</span> <span class="p">{</span> <span class="o">|</span><span class="n">chunk</span><span class="o">|</span> <span class="k">if</span> <span class="n">chunk</span><span class="p">.</span><span class="nf">type</span> <span class="o">==</span> <span class="s2">"acTL"</span> <span class="k">then</span> <span class="nb">p</span> <span class="n">chunk</span> <span class="k">end</span> <span class="p">}</span>
<span class="c1">#&lt;ChunkyPNG::Chunk::Generic:0x0000010160f570 @type="acTL",</span>
  <span class="vi">@content</span><span class="o">=</span><span class="s2">"</span><span class="se">\\</span><span class="s2">x00</span><span class="se">\\</span><span class="s2">x00</span><span class="se">\\</span><span class="s2">x00</span><span class="se">\\</span><span class="s2">x01</span><span class="se">\\</span><span class="s2">x00</span><span class="se">\\</span><span class="s2">x00</span><span class="se">\\</span><span class="s2">x00</span><span class="se">\\</span><span class="s2">x01"</span><span class="o">&gt;</span>
</code></pre></div></div>

<p>Can you see the <code class="language-plaintext highlighter-rouge">acTL</code> block?
It isn’t in the <a href="http://www.w3.org/TR/PNG/">PNG specification</a>.
After a few minutes of Googling, it was clear that this block is only available in a <a href="https://wiki.mozilla.org/APNG_Specification">related file format called APNG</a>, an extension to PNG enabling animated images similar to GIF.</p>

<p>The first byte <code class="language-plaintext highlighter-rouge">@content</code> of the <code class="language-plaintext highlighter-rouge">acTL</code> block is the number of frames (only 1) and the second one is how many times to loop the APNG (<a href="https://wiki.mozilla.org/APNG_Specification#.60acTL.60:_The_Animation_Control_Chunk">source</a>). 
From the <a href="https://wiki.mozilla.org/APNG_Specification">spec</a>, there’s always a “default image” (described by the <code class="language-plaintext highlighter-rouge">IDAT</code> blocks, exactly like a normal PNG file), thus this extra frame should be the second Psyduck image.</p>

<p>To confirm this hypothesis, I installed <a href="pngcrush">pngcrush</a> and removed the <code class="language-plaintext highlighter-rouge">acTL</code> block:</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>brew <span class="nb">install </span>pngcrush

<span class="c"># Remove the `acTL` block from psyduck-original.png.</span>
<span class="nv">$ </span>pngcrush <span class="nt">-rem</span> acTL psyduck-original.png psyduck-no-animation.png
</code></pre></div></div>

<p>I ended up with an image that will look the same independent of the host browser:</p>

<figure>
  <img src="/images/psyduck-altered.png" alt=" Psyduck image altered so it looks the same independent of which browser is used to open it" />
  
</figure>

<p>Searching led me to the cause of the discrepancy: only Firefox and Opera have support for APNG files!
From <a href="https://productforums.google.com/forum/#!topic/chrome/6BVLS-kEIic">this post in Google’s Products forum</a> and <a href="https://code.google.com/p/chromium/issues/detail?id=1171&amp;q=apng&amp;colspec=ID%20Pri%20Mstone%20ReleaseBlock%20Area%20Feature%20Status%20Owner%20Summary">this ticket in Chromium’s issue tracker</a>, WebKit/Chrome doesn’t support the format and probably won’t for some time.
Also, from the <a href="https://wiki.mozilla.org/APNG_Specification">spec</a>:</p>

<blockquote>
  <p>APNG is backwards-compatible with PNG; any PNG decoder should be able to ignore the APNG-specific chunks and display a single image.</p>

  <footer>The APNG specification.</footer>
</blockquote>

<p>The mystery is solved: the image is correctly treated as an APNG when opened on Firefox, the animation runs for 1 frame, and you see the second frame.
On the other hand, in Chrome/Safari, it is read as a normal PNG, the extra block is ignored, and the default image is shown.</p>

<p>That was fun.</p>

<h2 id="references">References</h2>

<ol>
  <li><a href="http://www.w3.org/TR/PNG/">The PNG specification</a></li>
  <li><a href="https://wiki.mozilla.org/APNG_Specification">The APNG specification</a></li>
  <li><a href="https://caniuse.com/apng">APNG support across browsers</a></li>
</ol>]]></content><author><name>Carlos Agarie</name></author><category term="investigation" /><category term="quirky" /><summary type="html"><![CDATA[Update (2026-02): The support for APNGs has improved considerably since this post was written. It was still an interesting investigation into PNGs so I’m going to leave it here.]]></summary></entry><entry><title type="html">How Jekyll works</title><link href="https://agarie.net.br/2012/10/how-jekyll-works.html" rel="alternate" type="text/html" title="How Jekyll works" /><published>2012-10-02T00:00:00-03:00</published><updated>2012-10-02T00:00:00-03:00</updated><id>https://agarie.net.br/2012/10/how-jekyll-works</id><content type="html" xml:base="https://agarie.net.br/2012/10/how-jekyll-works.html"><![CDATA[<p><strong>Update (2026-03-13)</strong>: This post is almost 14 years old!
The latest version of Jekyll when this text was written was <a href="https://github.com/jekyll/jekyll/tree/v0.11.2">0.11.2</a>, which didn’t include <a href="https://jekyllrb.com/docs/datafiles/">Data Files</a>, a feature for converting CSV/JSON/YAML files into items to include in the website; even so, I decided to keep it up due to the investigation being interesting in my opinion.
I rewrote a few paragraphs for clarity.</p>

<h2 id="the-quotes-generator">The Quotes Generator</h2>

<p>I use Tumblr to interact with fandoms I’m interested in.
One of the kinds of posts I enjoy are quotes from authors, which I use as a signal to find new books to read instead of book reviews.
Over time, these posts I reblogged grew into a decent-sized collection of quotes which I enjoy rereading from time to time, sort of like a “quotation book”.</p>

<p><a href="https://web.archive.org/web/20120207020314/http://jimpravetz.com/blog/2011/12/generating-jekyll-pages-from-data/">This post</a> shows the construction of a Generator plugin that updates a website with product information from a JSON file.
That inspired me to write something similar for myself: the plugin would pull data from Tumblr, save it onto a JSON file, then create a new page showcasing the quotes.</p>

<p>There’s an example of a generator plugin in the <a href="https://github.com/jekyll/jekyll/wiki/Plugins">GitHub wiki</a>:
<span class="marginnote">
    This example lived in the wiki on the repository before the documentation was moved to the <a href="https://jekyllrb.com/docs/plugins/">jekyllrb.com</a> website. (2026-03)
</span></p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">module</span> <span class="nn">Jekyll</span>
  <span class="k">class</span> <span class="nc">CategoryPage</span> <span class="o">&lt;</span> <span class="no">Page</span>
    <span class="k">def</span> <span class="nf">initialize</span><span class="p">(</span><span class="n">site</span><span class="p">,</span> <span class="n">base</span><span class="p">,</span> <span class="n">dir</span><span class="p">,</span> <span class="n">category</span><span class="p">)</span>
      <span class="vi">@site</span> <span class="o">=</span> <span class="n">site</span>
      <span class="vi">@base</span> <span class="o">=</span> <span class="n">base</span>
      <span class="vi">@dir</span> <span class="o">=</span> <span class="n">dir</span>
      <span class="vi">@name</span> <span class="o">=</span> <span class="s1">'index.html'</span>

      <span class="nb">self</span><span class="p">.</span><span class="nf">process</span><span class="p">(</span><span class="vi">@name</span><span class="p">)</span>
      <span class="nb">self</span><span class="p">.</span><span class="nf">read_yaml</span><span class="p">(</span><span class="no">File</span><span class="p">.</span><span class="nf">join</span><span class="p">(</span><span class="n">base</span><span class="p">,</span> <span class="s1">'_layouts'</span><span class="p">),</span> <span class="s1">'category_index.html'</span><span class="p">)</span>
      <span class="nb">self</span><span class="p">.</span><span class="nf">data</span><span class="p">[</span><span class="s1">'category'</span><span class="p">]</span> <span class="o">=</span> <span class="n">category</span>

      <span class="n">prefix</span> <span class="o">=</span> <span class="n">site</span><span class="p">.</span><span class="nf">config</span><span class="p">[</span><span class="s1">'category_title_prefix'</span><span class="p">]</span> <span class="o">||</span> <span class="s1">'Category: '</span>
      <span class="nb">self</span><span class="p">.</span><span class="nf">data</span><span class="p">[</span><span class="s1">'title'</span><span class="p">]</span> <span class="o">=</span> <span class="s2">"</span><span class="si">#{</span><span class="n">prefix</span><span class="si">}#{</span><span class="n">category</span><span class="si">}</span><span class="s2">"</span>
    <span class="k">end</span>
  <span class="k">end</span>

  <span class="k">class</span> <span class="nc">CategoryPageGenerator</span> <span class="o">&lt;</span> <span class="no">Generator</span>
    <span class="n">safe</span> <span class="kp">true</span>

    <span class="k">def</span> <span class="nf">generate</span><span class="p">(</span><span class="n">site</span><span class="p">)</span>
      <span class="k">if</span> <span class="n">site</span><span class="p">.</span><span class="nf">layouts</span><span class="p">.</span><span class="nf">key?</span> <span class="s1">'category_index'</span>
        <span class="n">dir</span> <span class="o">=</span> <span class="n">site</span><span class="p">.</span><span class="nf">config</span><span class="p">[</span><span class="s1">'category_dir'</span><span class="p">]</span> <span class="o">||</span> <span class="s1">'categories'</span>
        <span class="n">site</span><span class="p">.</span><span class="nf">categories</span><span class="p">.</span><span class="nf">keys</span><span class="p">.</span><span class="nf">each</span> <span class="k">do</span> <span class="o">|</span><span class="n">category</span><span class="o">|</span>
          <span class="n">site</span><span class="p">.</span><span class="nf">pages</span> <span class="o">&amp;</span><span class="n">lt</span><span class="p">;</span><span class="o">&amp;</span><span class="n">lt</span><span class="p">;</span> <span class="no">CategoryPage</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="n">site</span><span class="p">,</span> <span class="n">site</span><span class="p">.</span><span class="nf">source</span><span class="p">,</span>
                <span class="no">File</span><span class="p">.</span><span class="nf">join</span><span class="p">(</span><span class="n">dir</span><span class="p">,</span> <span class="n">category</span><span class="p">),</span> <span class="n">category</span><span class="p">)</span>
        <span class="k">end</span>
      <span class="k">end</span>
    <span class="k">end</span>
  <span class="k">end</span>
<span class="k">end</span>
</code></pre></div></div>

<p>After a bit of tinkering, I wrote a script that saved a JSON file locally with data pulled from Tumblr’s API, which was fed into a Generator plugin (<a href="https://gist.github.com/agarie/3816637">available here</a>) responsible for creating a page to showcase those quotes.
This process made me curious about how all those Markdown files are handled internally and I decided to investigate it further.
My first mental model involved different data structures holding all the generators, converters, &amp;c, that were invoked during the “compilation” of the site, but I still wanted to know how to generate an output HTML without an existing template, effectively creating it during “compile time”.</p>

<h2 id="exploration">Exploration</h2>

<p>I started with the Jekyll executable.
It’s pretty simple: the command-line parameters, the defaults and the <code class="language-plaintext highlighter-rouge">_config.yml</code> (through <code class="language-plaintext highlighter-rouge">Jekyll::configuration</code> method) are used to create an <code class="language-plaintext highlighter-rouge">options</code> hash and then a new site is instantiated:</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># Create the Site</span>
<span class="n">site</span> <span class="o">=</span> <span class="no">Jekyll</span><span class="o">::</span><span class="no">Site</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="n">options</span><span class="p">)</span>
</code></pre></div></div>

<p>After that, it starts watching the necessary directories if the <code class="language-plaintext highlighter-rouge">--auto</code> option was used:</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">if</span> <span class="n">options</span><span class="p">[</span><span class="s1">'auto'</span><span class="p">]</span>
  <span class="nb">require</span> <span class="s1">'directory_watcher'</span>
  <span class="nb">puts</span> <span class="s2">"Auto-regenerating enabled: </span><span class="si">#{</span><span class="n">source</span><span class="si">}</span><span class="s2"> -&gt; </span><span class="si">#{</span><span class="n">destination</span><span class="si">}</span><span class="s2">"</span>
    <span class="c1"># ...</span>
<span class="k">else</span>
  <span class="nb">puts</span> <span class="s2">"Building site: </span><span class="si">#{</span><span class="n">source</span><span class="si">}</span><span class="s2"> -&gt; </span><span class="si">#{</span><span class="n">destination</span><span class="si">}</span><span class="s2">"</span>
    <span class="c1"># ...</span>
<span class="k">end</span>
</code></pre></div></div>

<p>The site is built through a call to <code class="language-plaintext highlighter-rouge">#process</code>, the main method in the <code class="language-plaintext highlighter-rouge">Jekyll::Site</code> class.
Finally, it runs the local server if <code class="language-plaintext highlighter-rouge">--server</code> was specified.</p>

<p>Now to check how <code class="language-plaintext highlighter-rouge">#process</code> actually creates the website; onto <code class="language-plaintext highlighter-rouge">lib/jekyll/site.rb</code>:</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">process</span>
  <span class="nb">self</span><span class="p">.</span><span class="nf">reset</span>
  <span class="nb">self</span><span class="p">.</span><span class="nf">read</span>
  <span class="nb">self</span><span class="p">.</span><span class="nf">generate</span>
  <span class="nb">self</span><span class="p">.</span><span class="nf">render</span>
  <span class="nb">self</span><span class="p">.</span><span class="nf">cleanup</span>
  <span class="nb">self</span><span class="p">.</span><span class="nf">write</span>
<span class="k">end</span>
</code></pre></div></div>

<p>Let’s see what each of these methods do:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">reset</code>: initialize <code class="language-plaintext highlighter-rouge">Hash</code>es for the layouts, categories, and tags and <code class="language-plaintext highlighter-rouge">Array</code>s for the posts, pages, and static_files.</li>
  <li><code class="language-plaintext highlighter-rouge">read</code>: get site data from the filesystem and store it in internal data structures (both the ones created in the previous step and in the <code class="language-plaintext highlighter-rouge">Jekyll::Site</code> instance).</li>
  <li><code class="language-plaintext highlighter-rouge">generate</code>: call each generators’ <code class="language-plaintext highlighter-rouge">#generate</code> method.</li>
  <li><code class="language-plaintext highlighter-rouge">render</code>: call the <code class="language-plaintext highlighter-rouge">#render</code> method for each post and page.</li>
  <li><code class="language-plaintext highlighter-rouge">cleanup</code>: All pages, posts, and static_files are stored in a <code class="language-plaintext highlighter-rouge">Set</code> and everything else (unused files, empty directories) is deleted.</li>
  <li><code class="language-plaintext highlighter-rouge">write</code>: call the <code class="language-plaintext highlighter-rouge">#write</code> method of each post, page, and static_file, copying them to the destination folder (<code class="language-plaintext highlighter-rouge">_site</code> by default).</li>
</ul>

<p>To recap: to create a generator, I need to write a subclass of <code class="language-plaintext highlighter-rouge">Jekyll::Generator</code> with a <code class="language-plaintext highlighter-rouge">#generate</code> method; that would be called before rendering of the pages and posts.
However, I wanted to create the page for the quotes in-memory, which wasn’t playing so well with Jekyll’s rendering.
After reviewing how <code class="language-plaintext highlighter-rouge">#process</code> works, it is clear that whatever page I create needs to be included as if it was encountered during <code class="language-plaintext highlighter-rouge">#read</code>.</p>

<p>Thus, after creating a string representing a page filled with quotes obtained from <code class="language-plaintext highlighter-rouge">quotes.json</code>, I had to create a <code class="language-plaintext highlighter-rouge">Page</code> object and add it to the internal Jekyll’s data structures.
Consequently, <code class="language-plaintext highlighter-rouge">#render</code> will take that object and convert it into a complete HTML document that will be written to the filesystem via <code class="language-plaintext highlighter-rouge">#write</code>.
Here’s (part of) the code from the plugin that implements this sequence:</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">QuotesPage</span> <span class="o">&lt;</span> <span class="no">Page</span>
    <span class="nb">attr_accessor</span> <span class="ss">:content</span><span class="p">,</span> <span class="ss">:data</span>

    <span class="k">def</span> <span class="nf">initialize</span><span class="p">(</span><span class="n">site</span><span class="p">,</span> <span class="n">base</span><span class="p">,</span> <span class="n">dir</span><span class="p">,</span> <span class="nb">name</span><span class="p">)</span>
      <span class="vi">@site</span> <span class="o">=</span> <span class="n">site</span>
      <span class="vi">@base</span> <span class="o">=</span> <span class="n">base</span>
      <span class="vi">@dir</span>  <span class="o">=</span> <span class="n">dir</span>
      <span class="vi">@name</span> <span class="o">=</span> <span class="nb">name</span>

      <span class="nb">self</span><span class="p">.</span><span class="nf">process</span><span class="p">(</span><span class="nb">name</span><span class="p">)</span>
      <span class="nb">self</span><span class="p">.</span><span class="nf">content</span> <span class="o">=</span> <span class="s1">''</span>
      <span class="nb">self</span><span class="p">.</span><span class="nf">data</span> <span class="o">=</span> <span class="p">{}</span>
    <span class="k">end</span>
  <span class="k">end</span>

<span class="c1"># In Site#create_quotes_page</span>
<span class="n">quotes_page</span> <span class="o">=</span> <span class="no">QuotesPage</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="nb">self</span><span class="p">,</span> <span class="nb">self</span><span class="p">.</span><span class="nf">source</span><span class="p">,</span> <span class="s1">'quotes_page'</span><span class="p">,</span> <span class="s1">'index.html'</span><span class="p">)</span>

<span class="c1"># [...]</span>

<span class="c1"># `string` contains HTML generated from the quotes.json file</span>
<span class="n">quotes_page</span><span class="p">.</span><span class="nf">content</span> <span class="o">=</span> <span class="n">string</span>
<span class="n">quotes_page</span><span class="p">.</span><span class="nf">data</span><span class="p">[</span><span class="s2">"layout"</span><span class="p">]</span> <span class="o">=</span> <span class="nb">self</span><span class="p">.</span><span class="nf">config</span><span class="p">[</span><span class="s1">'tumblr_quotes_layout'</span><span class="p">]</span>

<span class="c1"># [...]</span>

<span class="nb">self</span><span class="p">.</span><span class="nf">pages</span> <span class="o">&lt;&lt;</span> <span class="n">quotes_page</span>
</code></pre></div></div>

<p>Check the code for the generator <a href="https://gist.github.com/3816637">at this gist</a> to understand what I’m saying.</p>

<p>I added a <a href="https://github.com/jekyll/jekyll/wiki/How-Jekyll-works">page to Jekyll’s repository wiki</a> based on this post.</p>

<h2 id="references">References</h2>

<ul>
  <li><a href="https://github.com/jekyll/jekyll">Jekyll on GitHub</a></li>
  <li><a href="https://web.archive.org/web/20120207020314/http://jimpravetz.com/blog/2011/12/generating-jekyll-pages-from-data/"><em>Generating Jekyll Pages From Data</em> on the Wayback Machine</a></li>
  <li>The original code for the Quotes Generator <a href="https://gist.github.com/agarie/3816637">lives in this gist</a>, but it’s incomplete.</li>
  <li><a href="https://jekyllrb.com/docs/datafiles/">Data files in modern Jekyll</a></li>
  <li><a href="https://jekyllrb.com/docs/plugins/">Plugins in modern Jekyll</a></li>
</ul>]]></content><author><name>Carlos Agarie</name></author><category term="programming" /><category term="jekyll" /><category term="ruby" /><summary type="html"><![CDATA[Update (2026-03-13): This post is almost 14 years old! The latest version of Jekyll when this text was written was 0.11.2, which didn’t include Data Files, a feature for converting CSV/JSON/YAML files into items to include in the website; even so, I decided to keep it up due to the investigation being interesting in my opinion. I rewrote a few paragraphs for clarity.]]></summary></entry></feed>