<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Ai-Tools on hippotion</title><link>https://blog.hippotion.com/tags/ai-tools/</link><description>Recent content in Ai-Tools on hippotion</description><generator>Hugo</generator><language>en-us</language><lastBuildDate>Fri, 02 Jan 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://blog.hippotion.com/tags/ai-tools/index.xml" rel="self" type="application/rss+xml"/><item><title>🫙 I Built a Tracker for My Kombucha. The Data Model Was the Hard Part.</title><link>https://blog.hippotion.com/posts/kombucha-tracker/</link><pubDate>Fri, 02 Jan 2026 00:00:00 +0000</pubDate><guid>https://blog.hippotion.com/posts/kombucha-tracker/</guid><description>Brewing kombucha looks simple until you try to model it: one batch splits into many flavored bottles, every jar generates a stream of pH and taste readings, and a SCOBY has a lineage. Here&amp;rsquo;s the little app I built to keep track — and why the schema, not the code, was the real work.</description><content:encoded><![CDATA[<h2 id="i-brew-kombucha">I brew kombucha</h2>
<p>If you haven&rsquo;t fallen down this hole: kombucha is sweet tea fermented by a SCOBY (a rubbery pancake of yeast and bacteria) into something tart and fizzy. It&rsquo;s a <em>living</em> hobby — the culture is alive, every batch is a little different, and the only way to get good is to pay attention and remember what you did.</p>
<p>I was not remembering what I did. Brew dates lived in my head, taste notes lived nowhere, and &ldquo;which jar was the ginger one again?&rdquo; was a genuine question I asked myself out loud, to a fridge.</p>
<p>So I built a tracker. It&rsquo;s called <strong>HipPotion</strong> — same family as everything else I run here. The brewing turned out to be the easy part. Modeling it was where it got interesting.</p>
<h2 id="why-a-simple-list-doesnt-fit">Why a simple list doesn&rsquo;t fit</h2>
<p>My first instinct was &ldquo;a batch is a row, log some notes.&rdquo; That falls apart fast, because kombucha isn&rsquo;t linear. It has two stages:</p>
<ul>
<li><strong>F1 (first ferment):</strong> the big jar of sweet tea + SCOBY, fermenting sour over a week or two. One vessel, one culture.</li>
<li><strong>F2 (second ferment):</strong> you split that sour base into bottles and flavor each one differently — ginger in this one, blackberry in that one, hibiscus in the next — then seal them to build carbonation.</li>
</ul>
<p>So <strong>one batch becomes many bottles, each with its own flavor, its own carbonation, its own outcome.</strong> A flat &ldquo;batch = row&rdquo; model can&rsquo;t express that. And on top of the branching, every jar and bottle produces a <em>stream</em> of observations over time: pH today, Brix tomorrow, &ldquo;tastes too sweet still&rdquo; the day after.</p>
<p>That&rsquo;s three different shapes at once — a lifecycle, a one-to-many split, and a time series — for what looks from the outside like &ldquo;I made some tea.&rdquo;</p>
<h2 id="the-model-i-landed-on">The model I landed on</h2>
<p>Six tables, each earning its place:</p>
<ul>
<li><strong><code>recipes</code></strong> — the templates. Tea blend, sugar ratio, target numbers. A batch points at one.</li>
<li><strong><code>batches</code></strong> — an actual F1 brew, with a lifecycle (<code>planned → active → conditioning → finished</code>) and a reference to its recipe.</li>
<li><strong><code>fermentation_log_entries</code></strong> — the time series. One row per observation per batch: pH, Brix, temperature, taste/smell notes, what I did. This is where the &ldquo;pay attention and remember&rdquo; lives.</li>
<li><strong><code>f2_variant_batches</code></strong> — the branch. Each is a flavored bottle split off a parent batch, tracked on its own.</li>
<li><strong><code>starter_log</code></strong> — SCOBY lineage. Cultures have parents; you grow new ones from old ones, and a sick culture ruins a batch, so the lineage matters.</li>
<li><strong><code>botanical_infusions</code></strong> — the flavoring ingredients, managed per recipe.</li>
</ul>
<p>The shape that took the longest to get right was the <strong>F1 → F2 split</strong>: a variant has to belong to its parent batch but live its own life. Once that relationship was clean, the whole thing clicked — the app finally matched how brewing <em>actually works</em> instead of how it&rsquo;s easy to store.</p>
<h2 id="the-stack-and-where-it-runs">The stack (and where it runs)</h2>
<p>Nothing exotic: React + Vite + TypeScript on the front (TanStack Query, shadcn/ui, Tailwind), a <a href="https://hono.dev">Hono</a> + Drizzle ORM API on the back, PostgreSQL underneath. Built with AI coding tools — I leaned on them hard for the React/shadcn front-end, less so for the schema, which I argued out by hand because it&rsquo;s the part that had to be <em>right</em>.</p>
<p>It runs on my k3s homelab like everything else: a Helm chart deploys the nginx frontend, the Hono API, and a Postgres StatefulSet, all reconciled by Argo CD from Git. Default-deny networking, secrets out of Git — the <a href="/posts/homelab-gitops/">usual platform defaults</a>. It&rsquo;s a hobby app, but it gets treated like a real one, because the platform doesn&rsquo;t know the difference and I don&rsquo;t want it to.</p>
<h2 id="it-became-an-api-for-something-else">It became an API for something else</h2>
<p>The unexpected payoff: because the data model was clean and the API was just a set of plain REST endpoints, it made a perfect target for an experiment. I later <a href="/posts/n8n-agent-cloud-vs-local/">pointed an AI agent at it from n8n</a> — &ldquo;what&rsquo;s fermenting right now?&rdquo;, &ldquo;log that this batch tastes tart&rdquo; — and the agent just called the same endpoints the UI does. A good schema is reusable in ways you don&rsquo;t plan for. The kombucha tracker quietly became a little knowledge base I can talk to.</p>
<h2 id="honest-notes">Honest notes</h2>
<p>This is a personal hobby app for an audience of one (me). It&rsquo;s AI-assisted, it has no tests, and the UI has rough edges. I&rsquo;m not pretending it&rsquo;s a product.</p>
<p>But the thing I keep coming back to: the hard, valuable part wasn&rsquo;t the framework or the deployment — it was sitting with a messy real-world process long enough to find the <em>shape</em> of it. The branching ferment, the time series, the lineage. Get the model honest and the rest is just typing. Get it wrong and no amount of nice UI saves you.</p>
<p>Also, the kombucha&rsquo;s been better since I started writing things down. Turns out the fridge wasn&rsquo;t a great database.</p>
]]></content:encoded></item><item><title>🎲 I Built a Browser Game to Learn AI Coding Tools. It Turned Into Something Else.</title><link>https://blog.hippotion.com/posts/dice-and-shrines/</link><pubDate>Fri, 04 Jul 2025 00:00:00 +0000</pubDate><guid>https://blog.hippotion.com/posts/dice-and-shrines/</guid><description>What started as a Claude Code / Codex sandbox became a territory conquest game with five asymmetric guardians, procedurally generated hex maps, and a stats service to balance them. Here&amp;rsquo;s what happened.</description><content:encoded><![CDATA[<h2 id="it-started-as-a-sandbox">It started as a sandbox</h2>
<p>I wanted to get a feel for AI-assisted coding tools — Claude Code, Codex — in a low-stakes environment where breaking things was fine. A browser game seemed like the right vehicle: self-contained, no prod database, no users to disappoint.</p>
<p>I picked a premise I knew was fun: <strong>Dice Wars</strong>. The Flash-era classic. Roll dice to attack adjacent territories, biggest army snowballs. Simple enough that I could focus on the tooling rather than the design. Or so I thought.</p>
<p>Six weeks later I had five asymmetric character classes, a procedural hex map generator with acceptance criteria, a FastAPI telemetry service recording every dice roll, and a stat dashboard I check more than I probably should. The tooling became a background concern. The game took over.</p>
<hr>
<h2 id="how-the-game-works">How the game works</h2>
<p>The rules are genuinely minimal.</p>
<p>You start with a random slice of a procedurally generated map — a patchwork of irregular coloured territories, each one a cluster of hex tiles that reads as a solid blob. You and up to seven opponents begin scattered across it. Objective: own everything.</p>
<p><strong>Attacking</strong> is one click. Select your territory, click an adjacent enemy territory. Both sides roll all their dice and sum. Higher total wins. Attacker wins: you capture the territory, your dice advance in minus one. Defender wins: your attack is repelled, you&rsquo;re reduced to a single die on the attacking territory.</p>
<p><strong>Reinforcements</strong> are the mechanic that makes this a strategy game. At the end of your turn, you receive bonus dice equal to the size of your <strong>largest contiguous group of territories</strong>. Not total territories — the biggest connected blob. Fragmented territory generates almost nothing. A solid connected bloc snowballs.</p>
<p>That one rule creates the entire strategic texture. Grab fast but stay connected. Chokepoints are worth defending at a loss. Cutting an opponent in half collapses their income immediately. The late game turns into tense standoffs until one roll cracks something open.</p>
<p><img alt="An 8-player Epic game in progress — territories changing hands, rankings shifting in the side panel" loading="lazy" src="/posts/dice-and-shrines/midgame.png"></p>
<hr>
<h2 id="the-shrines">The shrines</h2>
<p>Early on I added shrines — special territories marked with a ★. They behave differently from normal territories in a few ways:</p>
<ul>
<li><strong>Higher dice cap</strong>: normal territories max out at 8 dice, shrines at 10</li>
<li><strong>Minimum floor</strong>: a shrine never drops below 2 dice after attacking, win or loss — it can&rsquo;t be stripped bare</li>
<li><strong>Guaranteed reinforcement</strong>: the shrine gets a die first at end of turn, before random distribution</li>
<li><strong>Aura</strong>: each of your own territories adjacent to the shrine gets a +1 guaranteed die (shown with a dim ◆ indicator)</li>
</ul>
<p>The shrine mechanic does something interesting to the risk calculus. Holding a shrine isn&rsquo;t just a territory — it&rsquo;s a node that warps the value of everything adjacent to it. You&rsquo;ll defend an aura territory harder than a territory of equivalent size elsewhere on the map, because losing it means losing the aura bonus. It also means attacking <em>into</em> a shrine aura is expensive: the shrine can&rsquo;t be worn down easily, and the neighboring territories keep refilling.</p>
<p>Shrines turned out to be the moment the math got interesting.</p>
<hr>
<h2 id="five-guardians">Five guardians</h2>
<p>The game has a character select screen. Each player — human or AI — picks a guardian before the map generates. Five options, each with a passive and an active ability that meaningfully change how you play:</p>
<p><strong>Hippo</strong> gets to manually place one reinforcement die each turn before the rest distribute randomly. One die placement doesn&rsquo;t sound like much until you realise you always have a frontline territory that needs it more than anywhere else. High floor, consistent.</p>
<p><strong>Hedgehog</strong> fills weakest territories first during reinforcement. Defensively solid — you never bleed out a border territory to zero while inland territories sit at cap. It doesn&rsquo;t generate more dice, but it wastes fewer.</p>
<p><strong>Fox</strong> banks a stored die every other turn and can spend it as a critical multiplier on an attack. The stored dice accumulate (up to a cap), so the power compounds if you resist using it. Two-hop flanking passive: Fox can attack across two territory hops from border territories, making it very hard to feel safely tucked away from.</p>
<p><strong>Owl</strong> has a passive two-hop attack range like Fox but for all attacks — Owl sees further. The active ability is a Dice Transfer: move dice from one of your territories to an adjacent friendly one. Lets you concentrate force without committing to an attack.</p>
<p><strong>Turtle</strong> gets 2 dice back to a neighboring territory any time it loses a defense. It&rsquo;s the only guardian that turns <em>taking damage</em> into a resource. Hard to snowball, but very hard to finish off.</p>
<p>The AI cycles through the five guardians deterministically, so in a full 8-player game you&rsquo;ll face at least one of each.</p>
<hr>
<h2 id="the-map">The map</h2>
<p>The map generator produces irregular coloured territories from a hex grid. Hexes are the visual scaffolding — what matters in gameplay is the blob they form. Internal edges within a territory are hidden; only the outer border of each cluster is drawn. The result reads like a contested piece of ground rather than a grid.</p>
<p><img alt="A freshly generated map before the first move — eight starting positions across irregular territory blobs" loading="lazy" src="/posts/dice-and-shrines/freshmap.png"></p>
<p>The generator has acceptance criteria. A proposed map is rejected if territory sizes are too uneven — a tiny starting territory with 2 hexes versus a sprawling 15-hex territory produces a wildly unfair game. The stats service actually records generation attempts and acceptance rates per map, so I can see how often the map generator throws away its own work.</p>
<p>The <code>mapId</code> is stamped on every game record, so I can eventually correlate map topology with game outcomes. I haven&rsquo;t done that analysis yet, but the data is there.</p>
<hr>
<h2 id="what-the-tools-actually-felt-like">What the tools actually felt like</h2>
<p>Claude Code handles the kind of work that benefits from holding a lot of context simultaneously: &ldquo;this change to <code>resolveAttack()</code> in <code>game.js</code> needs corresponding updates to the AI logic in <code>ai.js</code> and the render pass in <code>render.js</code>.&rdquo; That&rsquo;s tedious to track manually and exactly the kind of thing where the tool earns its keep.</p>
<p>Codex was more useful for the boilerplate-heavy parts — filling in schema.sql, wiring up FastAPI endpoints, the chart.js setup in the dashboard. Directed generation of code you know exactly how should look.</p>
<p>Neither tool replaces thinking. The guardian ability design, the shrine balance, the question of whether Fox&rsquo;s stored critical is too swingy in epic mode — that&rsquo;s all still just sitting down and working it out. The tools speed up the translation from &ldquo;I know what I want&rdquo; to &ldquo;here is working code.&rdquo; The game design itself doesn&rsquo;t compress.</p>
<hr>
<h2 id="its-live">It&rsquo;s live</h2>
<p>The game runs at <a href="https://dice.hippotion.com">dice.hippotion.com</a>. Single HTML file served from a Kubernetes ConfigMap on my homelab. No accounts, no install. It runs fast — AI turns are instant when you toggle off animations, and a full game can be over in five minutes or stretch to twenty depending on how the map falls.</p>
<p>Stats dashboard at <a href="https://game-stats.hippotion.com">game-stats.hippotion.com</a>. More on that in the next post.</p>
]]></content:encoded></item></channel></rss>