<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>I know what you're thinking... on Man-You</title><link>https://man-you.ringum.net/</link><description>Recent content in I know what you're thinking... on Man-You</description><generator>Hugo -- gohugo.io</generator><language>en-us</language><lastBuildDate>Wed, 06 May 2026 22:30:00 +0200</lastBuildDate><atom:link href="https://man-you.ringum.net/index.xml" rel="self" type="application/rss+xml"/><item><title>プログラマ道 (puroguramā-dō)</title><link>https://man-you.ringum.net/posts/programmer-way/</link><pubDate>Wed, 06 May 2026 22:30:00 +0200</pubDate><guid>https://man-you.ringum.net/posts/programmer-way/</guid><description>&lt;p&gt;プログラマ道&lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;Companion piece to the &lt;a href="https://man-you.ringum.net/posts/100x-engineer/"&gt;100x post&lt;/a&gt;. That one was the framing argument: AI tooling is &lt;code&gt;+0.99&lt;/code&gt; with a wielding bonus, not a multiplier. This is the ground-level version. How I actually drive the thing day to day, what I will not delegate, and why none of this is new.&lt;/p&gt;
&lt;p&gt;I drive Claude every day. Across Woosmap services in Python, a music app in Go and SwiftUI on the weekends (Tunes), a SNES toolchain in Python (a816, xdds), and a fair amount of 65c816 ROM hacking. The tool is real. It does not flip the table on the discipline an engineer needs. It raises the floor a notch and rewards the wielder. What the wielder is actually doing, when it works, is the part nobody writes down.&lt;/p&gt;
&lt;h2 id="what-blindness-looks-like"&gt;What blindness looks like&lt;/h2&gt;
&lt;p&gt;Drop Claude into a fresh codebase and watch it grep. It reads filenames, opens files, follows imports by string match, guesses at module boundaries. On a small repo this is fine. On anything real it degrades into expensive guessing. Python projects make it worse: the source of truth for a symbol&amp;rsquo;s type is often the installed package, not the repo, and the model cannot see installed packages. So it produces plausible code against an imagined API.&lt;/p&gt;
&lt;p&gt;The fix is not a better prompt. The fix is to give the model the same thing a human gets: a language server. Once basedpyright is wired up and Claude can ask &amp;ldquo;what is this symbol, where is it defined, what type does it return&amp;rdquo;, the questions get sharper and the answers stop being invented. The model does not need to be smarter. It needs to stop being blind.&lt;/p&gt;
&lt;p&gt;Our internal stack runs in containers, no local virtualenv, dependencies live inside the image. A model on the host sees nothing. The fix we use internally is a sidecar container that exposes a language server with access to the real installed packages, attached to the application container. Once that is in place, Claude stops hallucinating signatures. Same model, same prompts, dramatically less drift. The intelligence was never the bottleneck. The view was.&lt;/p&gt;
&lt;h2 id="the-testbed-problem-named-on-a-snes"&gt;The testbed problem, named on a SNES&lt;/h2&gt;
&lt;p&gt;ROM hacking is notoriously trial-and-error&lt;sup id="fnref:2"&gt;&lt;a href="#fn:2" class="footnote-ref" role="doc-noteref"&gt;2&lt;/a&gt;&lt;/sup&gt;. The CPU does not care about your intent. The PPU cares even less. A bug in HDMA timing is invisible until you stare at the right cycle on the right scanline, and the only way to know you fixed it is to watch the framebuffer change. There is no type system saving you. There is no test framework that ships in the box.&lt;/p&gt;
&lt;p&gt;So I built one. kintsuki (yes, typo of kintsugi, name stuck) embeds ares (a SNES emulator) as a C library, exposes Python bindings on top, and gives me programmatic control of the emulator: step execution, trace, read memory, write memory, hook the per-frame interrupt, dump CPU and video memory, diff framebuffers. From there I write pytest cases that drive the ROM to a known state, assert on the bytes that should have changed, and fail loudly when they did not. Regression testing for ROM hacks. With kintsuki in the loop, Claude can iterate. It edits the asm, pytest runs the ROM, the snapshot comes back, the test says green or red.&lt;/p&gt;
&lt;p&gt;This is the pattern in general. When the model gets stuck in a loop, almost always it is not a reasoning failure. It is a feedback failure. Build the testbed before you blame the prompt. A pytest case that reproduces the bug. A shell one-liner that exercises the endpoint. A snapshot test that goes red on the broken behavior. Whatever shape it takes in your domain. Once it exists, the model converges. Same iteration loop a competent human uses. Without it, you get hallucinated success.&lt;/p&gt;
&lt;p&gt;For the SNES work I started with Mesen Lua scripts, which is the standard answer in the romhacking community. Useful for one-off probing, painful for regression testing. Lua is interpreted inside the emulator, the test harness lived outside the emulator, and the seam between them was where bugs hid. kintsuki replaced that whole arrangement with the emulator itself as a library called from pytest. One process, one language, one trace of execution. The Mesen scripts taught me what to want. Building kintsuki was admitting that the standard tool had hit its ceiling.&lt;/p&gt;
&lt;p&gt;TDD plays well with the model for the same reason. The red test is the bar. &amp;ldquo;Done&amp;rdquo; is defined externally instead of by whatever sounds done.&lt;/p&gt;
&lt;h2 id="time-to-serve"&gt;Time to serve&lt;/h2&gt;
&lt;p&gt;The number I find useful for measuring whether the tool is moving anything is time to serve&lt;sup id="fnref:3"&gt;&lt;a href="#fn:3" class="footnote-ref" role="doc-noteref"&gt;3&lt;/a&gt;&lt;/sup&gt;. From the moment a problem is articulated to the moment the fix is in production, end to end. Not lines of code, not commits per day, not tokens consumed.&lt;/p&gt;
&lt;p&gt;It captures the whole pipeline in one denominator. The parts the model speeds up (typing, boilerplate, first cuts) and the parts it does not (deciding what to build, the data model, the testbed, the diff review, CI, production). If the tool is genuinely net positive, the number drops. If the model is producing more code while the rest of the pipeline absorbs the cost, the number stays flat. The metric does not flatter the tool. It measures the wielder using the tool.&lt;/p&gt;
&lt;p&gt;Worth pairing with the &lt;a href="https://man-you.ringum.net/posts/100x-engineer/#the-token-economics-nobody-is-pricing-in"&gt;token economics piece&lt;/a&gt; from the public post. Time to serve is the team metric today. Cost per shipped feature is the same shape once tokens stop being subsidized.&lt;/p&gt;
&lt;h2 id="what-i-do-not-delegate"&gt;What I do not delegate&lt;/h2&gt;
&lt;p&gt;This is the 道 part&lt;sup id="fnref:4"&gt;&lt;a href="#fn:4" class="footnote-ref" role="doc-noteref"&gt;4&lt;/a&gt;&lt;/sup&gt;. The places where I refuse to hand the wheel over, regardless of how confident the model sounds.&lt;/p&gt;
&lt;p&gt;Architecture choices the model makes silently. I have shipped diffs where the model swapped a lifecycle (background task vs request-bound, lazy vs eager init, sync vs async at a boundary) without flagging the consequence. The change was not wrong-looking. It was just a different decision than the one I asked for, embedded in code that read as ordinary. That kind of thing slips past self-review. Human PR review by someone outside the loop with the model is still load-bearing.&lt;/p&gt;
&lt;p&gt;Platform debugging. The general-purpose memory showed the rolling inventory animating correctly while the video memory stayed wrong: the data was right, the upload to the screen was not. The hypothesis (the per-scanline DMA was pointing at the wrong background layer during the only window each frame when the picture chip lets you write to it) was not in any prompt. The model coded the patch once I gave it the algorithm. It could not have formed the algorithm from a screenshot.&lt;/p&gt;
&lt;p&gt;Data model and naming. The model defaults to plausible names that collide in app code, or to over-typing every parameter, or to stringly-typing everything. The boundary calls are mine. So is deciding when a function should not exist at all.&lt;/p&gt;
&lt;p&gt;The model writes code. Choosing what should exist, what it is called, what invariants it preserves, that is still the job.&lt;/p&gt;
&lt;h2 id="getting-oversmarted"&gt;Getting oversmarted&lt;/h2&gt;
&lt;p&gt;The way the model wins against me, when it does, is not by being cleverer. It is by me losing track of what got built. Five tool calls deep, a refactor I did not quite ask for slipped in alongside the fix I did ask for, tests pass, diff looks reasonable. Sign off and the surprise lands a week later.&lt;/p&gt;
&lt;p&gt;Countermove is the same rule I use on myself. Faut faire qui marche avant que c&amp;rsquo;est beau. Works for the model too. Let it go from A to B with the ugly first cut. Don&amp;rsquo;t pre-optimize the route, don&amp;rsquo;t stop it for a paint job halfway through. Then read the diff with eyes on. If on the road I see something starting to jeopardize the destination, that is the signal: I asked for something not clear enough. The drift is feedback on my prompt, not on the model.&lt;/p&gt;
&lt;p&gt;The dog metaphor is the cleanest. Go fetch. The dog will fetch. If you said go fetch and pointed vaguely, the dog comes back with a stick when you wanted the ball. Not the dog&amp;rsquo;s fault. Throw better, or accept whatever comes back.&lt;/p&gt;
&lt;p&gt;Discipline is two things. Keep the destination visible to yourself the whole time, so you notice when the path bends. And reread the diff. The model is fast enough that the only person who can lose track of what got built is you.&lt;/p&gt;
&lt;h2 id="ask-claude-to-review-claude"&gt;Ask Claude to review Claude&lt;/h2&gt;
&lt;p&gt;I regularly ask the model to look at its own work with fresh eyes. It catches dead branches, redundant guards, mocks that should be fixtures, signatures that drifted from the call sites.&lt;/p&gt;
&lt;p&gt;No costume preamble, no &amp;ldquo;you are now a senior reviewer&amp;rdquo; framing. I am not a jeu de rôle grandeur nature guy. Played RPGs as video games, plenty, but I do not run my prompts like sessions at a tabletop. Why would I. In the same week I am a programmer, a CTO, a romhacker, a JS/TS dev, a reviewer, an ops guy. The hat changes with the task, no costume needed. Claude is the same. Ask it to look again, it does.&lt;/p&gt;
&lt;p&gt;The act of generating and the act of judging are not the same operation, and forcing the second pass cheaply pulls out the obvious mistakes before they reach a human reviewer. Not a substitute for that reviewer. A filter that respects their time.&lt;/p&gt;
&lt;h2 id="same-workflow-as-with-a-coworker"&gt;Same workflow as with a coworker&lt;/h2&gt;
&lt;p&gt;Working with the model is not that different from working with a teammate. I look for friction, usually starting with my own pain, and I build something to smooth it. My job has been building tools for developers for a long time, and it turns out Claude hits the same walls a human dev hits: bad imports, missing types, no testbed, no probe into the running system. Solve those for the human and the model gets the fix for free.&lt;/p&gt;
&lt;p&gt;The LSP sidecar from earlier is the cleanest example. Built it because I was losing time to the model not seeing installed packages, but the same sidecar makes any human on the team more productive in the same repos. kintsuki is the same shape: deterministic snapshots so I could reason about HDMA, and the model uses them too.&lt;/p&gt;
&lt;p&gt;You cannot build high on crappy foundations. That has been true with humans for decades and the model does not change it. If anything it makes the foundations more visible, because the model is brutal at exposing the soft spots in your dev loop.&lt;/p&gt;
&lt;p&gt;Hard position to defend in rooms where AI is supposed to change the world. I see it as a tool. If typing on a keyboard were the programmer&amp;rsquo;s job we would already have been replaced by typists, who can produce an order of magnitude more words per minute than any of us. We were not. The keyboard was never the bottleneck. It is not the bottleneck now either.&lt;/p&gt;
&lt;h2 id="the-道-has-been-written-down-for-thirty-years"&gt;The 道 has been written down for thirty years&lt;/h2&gt;
&lt;p&gt;The instincts in this post are not new. Two books in particular keep mapping onto what I do with the model.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Growing Object-Oriented Software, Guided by Tests&lt;/em&gt;&lt;sup id="fnref:5"&gt;&lt;a href="#fn:5" class="footnote-ref" role="doc-noteref"&gt;5&lt;/a&gt;&lt;/sup&gt; is the testbed argument with the receipts. Freeman and Pryce wrote it for human teams trying to keep design honest as a system grows. Same instincts hold when the author is a model: start from a failing test, let the test shape the interface, refactor under green. Tests as the design surface, not just the safety net.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;The Pragmatic Programmer&lt;/em&gt;&lt;sup id="fnref:6"&gt;&lt;a href="#fn:6" class="footnote-ref" role="doc-noteref"&gt;6&lt;/a&gt;&lt;/sup&gt; is the other. Hunt and Thomas have a chapter called &lt;em&gt;Programming by Coincidence&lt;/em&gt; that names the failure mode I push back on hardest with the model: code that works without the author understanding why. Their &lt;em&gt;tracer bullets&lt;/em&gt; idea is the same instinct as faut faire qui marche avant que c&amp;rsquo;est beau, fire something end to end first, see where it lands, adjust aim.&lt;/p&gt;
&lt;p&gt;Both books predate the model by decades and apply to it without modification. The 道 was written down a long time ago. The model is the newest student in the dojo, not the founder of a new school. If a tool reframes the discipline so completely that the canon stops applying, the canon was wrong. So far, the canon is fine.&lt;/p&gt;
&lt;h2 id="what-i-tell-people-who-ask"&gt;What I tell people who ask&lt;/h2&gt;
&lt;p&gt;When the model and I disagree, I am right more often than not, and the times I am wrong it is because I delegated something I should have owned. That is the calibration. Not trust the model, not distrust the model. Trust your own ability to recognize when the output is wrong, and treat the model&amp;rsquo;s confidence as decoration.&lt;/p&gt;
&lt;p&gt;プログラマ道&lt;sup id="fnref1:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt; is the same job it has always been. The tools changed. The discipline did not.&lt;/p&gt;
&lt;div class="footnotes" role="doc-endnotes"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;Programmer plus 道, the way or path. Same suffix as 柔道 (jūdō, the gentle way), 剣道 (kendō, the way of the sword), 茶道 (sadō, the way of tea). Practice you keep refining, not a credential you finish.&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&amp;#160;&lt;a href="#fnref1:1" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:2"&gt;
&lt;p&gt;SNES vocabulary used in this section, glossed once: &lt;strong&gt;CPU&lt;/strong&gt; is the 65c816 main processor. &lt;strong&gt;PPU&lt;/strong&gt; is the picture processing unit, a fixed-function chip that composes the picture from sprites and tile layers (not a GPU in the modern sense, no shaders, no general compute). &lt;strong&gt;WRAM&lt;/strong&gt; is general-purpose memory the CPU writes to. &lt;strong&gt;VRAM&lt;/strong&gt; is the PPU&amp;rsquo;s separate memory, only writable through narrow windows each frame. &lt;strong&gt;HDMA&lt;/strong&gt; is a per-scanline DMA mechanism that lets you change PPU registers during display. &lt;strong&gt;NMI&lt;/strong&gt; is the non-maskable interrupt that fires once per frame, the standard window for VRAM updates. &lt;strong&gt;BG3&lt;/strong&gt; is one of the four background layers the PPU composes. The point of the article does not depend on the details, but the jargon is real and so are the bugs it produces.&amp;#160;&lt;a href="#fnref:2" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:3"&gt;
&lt;p&gt;Not sure who coined &amp;ldquo;time to serve&amp;rdquo;. Closest canonical sibling is DORA&amp;rsquo;s &lt;em&gt;lead time for changes&lt;/em&gt; (Forsgren, Humble, Kim, &lt;em&gt;Accelerate&lt;/em&gt;). I have been using &lt;em&gt;time to serve&lt;/em&gt; internally without a clean attribution. If you know the source, tell me and I will update this footnote.&amp;#160;&lt;a href="#fnref:3" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:4"&gt;
&lt;p&gt;道 on its own. The way. What stays yours after the tooling moves under your feet.&amp;#160;&lt;a href="#fnref:4" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:5"&gt;
&lt;p&gt;Steve Freeman and Nat Pryce, &lt;em&gt;Growing Object-Oriented Software, Guided by Tests&lt;/em&gt;, Addison-Wesley 2009. The &amp;ldquo;guided by tests&amp;rdquo; half is the part that ages best. Tests as the design surface, not just the safety net.&amp;#160;&lt;a href="#fnref:5" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:6"&gt;
&lt;p&gt;Andy Hunt and Dave Thomas, &lt;em&gt;The Pragmatic Programmer&lt;/em&gt;, Addison-Wesley 1999, 20th anniversary edition 2019. &lt;em&gt;Programming by Coincidence&lt;/em&gt; and &lt;em&gt;Tracer Bullets&lt;/em&gt; are the two chapters that map cleanest onto working with an LLM.&amp;#160;&lt;a href="#fnref:6" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</description></item><item><title>The 100x engineer, and the unit nobody defines</title><link>https://man-you.ringum.net/posts/100x-engineer/</link><pubDate>Mon, 04 May 2026 01:03:42 +0200</pubDate><guid>https://man-you.ringum.net/posts/100x-engineer/</guid><description>&lt;p&gt;The 100x engineer is back in the discourse. This time the pitch is that an engineer with the right AI tooling can do the work of a hundred. Sometimes the number is 10x, sometimes it is 100x, occasionally someone goes wild and writes 1000x. The number is always round and the unit is always missing.&lt;/p&gt;
&lt;p&gt;What are we counting?&lt;/p&gt;
&lt;p&gt;If a 100x engineer using Claude can do 365 days of work in 3.65 days, whose 365 days? A staff engineer shipping platform infrastructure? A junior wiring up CRUD endpoints? My grandmother on a good day? The denominator is doing all the work in that sentence and nobody ever writes it down.&lt;/p&gt;
&lt;h2 id="the-rpg-item-problem"&gt;The RPG item problem&lt;/h2&gt;
&lt;p&gt;There is a cleaner way to frame this. The &amp;ldquo;100x engineer&amp;rdquo; pitch sells AI tooling as a multiplier, the way RPG loot sells itself as &lt;code&gt;*100 to all stats&lt;/code&gt;. The math of a multiplier compounds the floor: if your stats are 0.01, a &lt;code&gt;*100&lt;/code&gt; item brings you to 1. If your stats are 8, the same item flings you to 800. The marketing wants you to imagine the second case. The buyer is usually the first.&lt;/p&gt;
&lt;p&gt;What AI tooling actually behaves like is a flat-add item. Roughly &lt;code&gt;+0.99 to all stats&lt;/code&gt;. The kid at 0.01 puts it on and lands exactly at 1.00. The senior at 8 puts it on and lands at 8.99. Same buff, applied to whoever wears it.&lt;/p&gt;
&lt;p&gt;Except a flat-add understates one thing: the senior extracts more from the same item. They know what to ask for, recognise when the output is wrong, slot the working bits into a system the kid does not have yet. The base buff is the same; what the wearer does with it scales with their level. Closer to &lt;code&gt;+0.99 base, with a wielding bonus&lt;/code&gt;. Still not a multiplier on stats. The buff is on judgment, not on raw capability.&lt;/p&gt;
&lt;p&gt;When someone tells me a terminal and Claude Code made them 100x, the question is not snark. What was the floor? If the AI item moved you from &amp;ldquo;could not ship a working CRUD app in a week&amp;rdquo; to &amp;ldquo;can ship one in an afternoon,&amp;rdquo; that is the &lt;code&gt;+0.99&lt;/code&gt; doing exactly what &lt;code&gt;+0.99&lt;/code&gt; does. Not a 100x engineer. A &lt;code&gt;+0.99&lt;/code&gt; wearer who started at 0.01.&lt;/p&gt;
&lt;p&gt;The 100x story flatters more than &amp;ldquo;I was very junior and my tools got better.&amp;rdquo; Same story. Tells you nothing about the ceiling, only about the floor the speaker was sitting on.&lt;/p&gt;
&lt;p&gt;A multiplier without a base is a vibe, not a measurement.&lt;/p&gt;
&lt;h2 id="a-concrete-counter-example"&gt;A concrete counter-example&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://man-you.ringum.net/posts/tunes/"&gt;Tunes&lt;/a&gt; is the music app and backend I have been building on weekends, a Go service plus SwiftUI clients on iOS, macOS, and tvOS, replacing iTunes Match for my own library. AI has been in the loop on almost every commit. I am close to a year of weekend development and the thing is not shipped.&lt;/p&gt;
&lt;p&gt;If 100x were real, Tunes should have been done sometime around last February. It was not. The reason is not that I was idle. Building a real product is mostly the parts AI does not compress: deciding the data model, debugging a SwiftUI animation that only misbehaves on tvOS, understanding why the Go service drops connections on a specific HLS edge case, designing the play queue semantics, redesigning them after I lived with the first version for a month.&lt;/p&gt;
&lt;p&gt;The model writes the function. I still have to know which function to ask for. Otherwise I get a bubble sort hidden inside a request handler that takes 45 seconds for no good reason, and the model will defend it with a straight face until I push back.&lt;/p&gt;
&lt;p&gt;AI maggots will read this and tell me it is a &lt;code&gt;SKILL.md&lt;/code&gt; issue, that the model would have known better with the right rules file pinned to the context. Sure. It is a skill issue. Just not the kind they mean. The wielding bonus shows up here.&lt;/p&gt;
&lt;h2 id="the-token-economics-nobody-is-pricing-in"&gt;The token economics nobody is pricing in&lt;/h2&gt;
&lt;p&gt;The unit cost of all this is currently subsidised. Frontier model access is sold below what it costs to serve, on top of training spend that has not been amortised. Investors are paying so we get to play with cheap intelligence. That ends. It always ends.&lt;/p&gt;
&lt;p&gt;When the price of a token starts to reflect what a token actually costs, the productivity story gets re-litigated against a less generous backdrop. The engineer who burned three thousand tokens flailing at the bubble sort and the engineer who spotted it in twenty seconds are doing very different work. Today they pay roughly the same. Tomorrow they will not. The boring metrics will show up: cost per shipped feature, cost per resolved bug, cost per regression caught before production. Management likes impressive marketing metrics. Field reality is usually a bit different.&lt;/p&gt;
&lt;p&gt;The engineer in that scenario is in the same position as the buyer of a smart speaker whose vendor pivots, or the company shipping on top of an open source project that changes direction. Different floor, same shape: you built on something you do not own. The &lt;a href="https://man-you.ringum.net/posts/complete-machine/"&gt;buyer floor&lt;/a&gt; and the &lt;a href="https://man-you.ringum.net/posts/supply-chain-control/"&gt;vendor floor&lt;/a&gt; live in other posts. The engineer floor is here.&lt;/p&gt;
&lt;p&gt;Real gain, yes. Not 100x, not 10x averaged across a real week. A &lt;code&gt;+0.99&lt;/code&gt; with a wielding bonus, applied to whoever wears the item. Sentence does not get retweeted, which is why it does not get shipped.&lt;/p&gt;
&lt;p&gt;Anyone using the 100x number is selling something or measuring something silly. If they are selling, ask what the unit is and watch them squirm. If they are measuring, ask them to print the denominator on the next slide.&lt;/p&gt;</description></item><item><title>25 years to a complete machine</title><link>https://man-you.ringum.net/posts/25-year-quest/</link><pubDate>Mon, 04 May 2026 00:08:13 +0200</pubDate><guid>https://man-you.ringum.net/posts/25-year-quest/</guid><description>&lt;p&gt;Since around the year 2000 I have been piling features into a French Final Fantasy IV translation patch. Variable-width fonts in dialog, in battle messages, in menu descriptions. Redrawn battle, load/save, main, and two-column magic menus. An expanded inventory of &lt;code&gt;0x48&lt;/code&gt; slots rendered through a paged window instead of the static grid the original engine assumed. Most of these were features the SNES ROM hacking scene of the era said could not be done on real hardware. Other hackers eventually pulled off equivalents on other titles, fair enough; modifying a compiled ROM is more convoluted than editing source code, but not impossible. Twenty-five years on, &amp;ldquo;impossible&amp;rdquo; reads as a fair approximation of how hard, not of whether.&lt;/p&gt;
&lt;figure&gt;&lt;img src="https://man-you.ringum.net/posts/25-year-quest/ff4-battle-french.png"
alt="Battle screen with French enemy names" width="400"&gt;
&lt;/figure&gt;
&lt;p&gt;The bugs that come with hard features are also recent, introduced by my own patches. The field-menu item list eventually crashed: open inventory, swap items around, and after enough operations the stack pointer drifted until the next interrupt landed on a &lt;code&gt;brk&lt;/code&gt;&lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt; instruction and the emulator stopped cold.&lt;/p&gt;
&lt;p&gt;The cause was one wrong return mnemonic&lt;sup id="fnref:2"&gt;&lt;a href="#fn:2" class="footnote-ref" role="doc-noteref"&gt;2&lt;/a&gt;&lt;/sup&gt;. &lt;code&gt;swap_redraw_trampoline&lt;/code&gt; lives in bank &lt;code&gt;$01&lt;/code&gt;&lt;sup id="fnref:3"&gt;&lt;a href="#fn:3" class="footnote-ref" role="doc-noteref"&gt;3&lt;/a&gt;&lt;/sup&gt;. Its real body, &lt;code&gt;swap_redraw_hook_impl&lt;/code&gt;, lives in bank &lt;code&gt;$21&lt;/code&gt;. The field menu calls the trampoline with &lt;code&gt;jsr.l&lt;/code&gt;&lt;sup id="fnref:4"&gt;&lt;a href="#fn:4" class="footnote-ref" role="doc-noteref"&gt;4&lt;/a&gt;&lt;/sup&gt;, which pushes a 24-bit return. The bank-$21 body returns via &lt;code&gt;rtl&lt;/code&gt;, which is correct. The trampoline itself ended in &lt;code&gt;rts&lt;/code&gt;, which is not. One byte of the long-return address stayed on the stack every time an item was swapped. Run the swap path enough times and the underflow cascaded, the next NMI&lt;sup id="fnref:5"&gt;&lt;a href="#fn:5" class="footnote-ref" role="doc-noteref"&gt;5&lt;/a&gt;&lt;/sup&gt; hit garbage, and the BRK trap I had wired into the vector table&lt;sup id="fnref:6"&gt;&lt;a href="#fn:6" class="footnote-ref" role="doc-noteref"&gt;6&lt;/a&gt;&lt;/sup&gt; caught the offending PC.&lt;/p&gt;
&lt;p&gt;The fix is one mnemonic. Getting to the point where I could see the wrong mnemonic took infrastructure.&lt;/p&gt;
&lt;h2 id="why-a-rolling-inventory-at-all"&gt;Why a rolling inventory at all&lt;/h2&gt;
&lt;p&gt;The rolling inventory was not strictly necessary. Two columns of items were getting tight on screen as the inventory grew, and I knew FF6 had solved a similar layout problem more elegantly with a paged renderer. FF4 and FF6 share engine roots, which made porting the technique tractable instead of ground-up work. So I lifted the approach.&lt;/p&gt;
&lt;p&gt;The memory side effect is real but secondary. The rolling render only keeps the visible slice in RAM and VRAM&lt;sup id="fnref:7"&gt;&lt;a href="#fn:7" class="footnote-ref" role="doc-noteref"&gt;7&lt;/a&gt;&lt;/sup&gt;, paging new ones in as the cursor scrolls. &lt;code&gt;0x48&lt;/code&gt; slots at roughly &lt;code&gt;0x40&lt;/code&gt; bytes of tile data each would have cost &lt;code&gt;0x48 * 0x40 = 0x1200&lt;/code&gt; bytes per buffer in a static layout. The rolling layout drops that to one window&amp;rsquo;s worth. If saving memory had been the only goal there were cheaper fixes than this one.&lt;/p&gt;
&lt;p&gt;The bug above was the price of pulling FF6 ergonomics into FF4: the trampoline that moves tile data between RAM and VRAM forgot to return with the right mnemonic, leaked one byte per swap, and the stack caught up with the player when they were having too much fun rearranging.&lt;/p&gt;
&lt;h2 id="what-made-the-bug-reachable"&gt;What made the bug reachable&lt;/h2&gt;
&lt;p&gt;For most of the 25 years I have been doing this, I would not have caught it.&lt;/p&gt;
&lt;p&gt;The original ff4 source, like every long-running ROM hack, was a single textual &lt;code&gt;.include&lt;/code&gt; chain compiled in one pass. Every label was global. There were no module boundaries. There was no way to ask the assembler &amp;ldquo;where does this symbol come from&amp;rdquo; except by grepping the file system. The trampoline-with-the-wrong-rts looked exactly like the trampoline-with-the-right-rtl two screens up. Lost in the noise.&lt;/p&gt;
&lt;p&gt;The ff4 source recently finished its migration to a816&amp;rsquo;s &lt;code&gt;.import&lt;/code&gt; system. Each module declares its own externs, compiles to its own &lt;code&gt;.o&lt;/code&gt;, links through relocations. The LSP earns its keep on top of that. When you suspect &lt;code&gt;swap_redraw_trampoline&lt;/code&gt; ends in &lt;code&gt;rts&lt;/code&gt; where it should end in &lt;code&gt;rtl&lt;/code&gt;, you do not grep for &amp;ldquo;swap_redraw&amp;rdquo; across hundreds of files. You click. The symbol resolves through the import graph, the trampoline opens in the right module, and the wrong mnemonic is on the screen.&lt;/p&gt;
&lt;p&gt;Without modules the bug was unreachable. Without the BRK trap in the vector table the symptom was &amp;ldquo;crashes sometimes&amp;rdquo; instead of a PC value. The rest of the chain (LSP for navigation, kintsuki for deterministic reproduction) only mattered once those two existed.&lt;/p&gt;
&lt;h2 id="why-these-tools-exist"&gt;Why these tools exist&lt;/h2&gt;
&lt;p&gt;I have been hacking on Final Fantasy IV, on and off, since around the year 2000. The earliest public trace is the &lt;a href="https://github.com/manz/ff4fr"&gt;ff4fr repo&lt;/a&gt; from 2011, itself a snapshot of older work. The reason the project keeps surviving long gaps is simple: it interests me. I come back, I poke at it for a few weekends, I drift, I come back. A hobby that happened to last.&lt;/p&gt;
&lt;p&gt;None of the tools were built as long-game preparation for the rolling inventory bug.&lt;/p&gt;
&lt;p&gt;a816 replaced x816.exe, the DOS assembler the FF4 scene had used for years. x816 is painful on Windows, dead on Linux and macOS. Building the ROM on GitHub Actions through dosbox was not going to happen. asar exists and is a fine alternative (and may not have been around when I started; I do not remember the timeline clearly). I went my own way because I wanted tight integration with the ff4 source: modules I controlled, a formatter I could shape, an LSP that understood bank annotations and docstring conventions, an object format that handled multi-region modules cleanly. Easier to build that end-to-end than to lobby someone else&amp;rsquo;s project for ergonomics specific to one ROM hack. The GitHub Actions side became its own win. Messing with workflows on a personal repo translated directly into knowledge I now apply at work.&lt;/p&gt;
&lt;p&gt;kintsuki exists because I like automated tests. Running the ROM deterministically against pinned inputs needs primitives ares does not expose by default: mid-frame yield, PC override across scheduler entries, &lt;code&gt;stp&lt;/code&gt; and &lt;code&gt;wai&lt;/code&gt; flag preservation through external state-set. kintsuki adds the shim around the ares core and ships it as a Python wheel.&lt;/p&gt;
&lt;p&gt;The honest reason underneath both: knowing enough to be dangerous has been the constant of my career. At fifteen, in 2000, I was dangerous with a hex editor and someone else&amp;rsquo;s assembler. That was already enough to start a translation patch. The version writing now is dangerous with a homemade assembler, an LSP, an emulator harness wrapped as a Python wheel, and a multi-region object format. The disposition has not moved. The capability has. Every year of doing software for a living added new ways of being dangerous, and the FF4 patch is the one personal project that stuck around long enough to absorb all of them. The tools and the patch grew up together. The features the fifteen-year-old wanted are now sitting on infrastructure he could not have built, but would absolutely have used.&lt;/p&gt;
&lt;p&gt;The thing actually pacing the work right now is a self-imposed release date: December 26, 2026, for a packaged build of the patch. That deadline turned the loose interest into a backlog. The deadline is recent. The hobby is old.&lt;/p&gt;
&lt;p&gt;Seven and a bit months until the release date. The patch will ship with bugs that came after it on top of an assembler and a harness that came before. The ROM hack will still have open issues, untranslated NPCs, and design decisions I will second-guess. None of that bothers me. It is the project I keep coming back to because it keeps being interesting, and right now the interesting thing is finishing it.&lt;/p&gt;
&lt;p&gt;Not everything I write is meant to stick it up to the man. This one is not. Hobby code, on a console nobody ships for, against a deadline I made up because I felt like one. No thesis. A release on December 26.&lt;/p&gt;
&lt;div class="footnotes" role="doc-endnotes"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;&lt;code&gt;brk&lt;/code&gt; is a one-byte instruction that triggers a software interrupt. The CPU pushes its state and jumps to a fixed handler address. Useful as a trap when that handler is wired to halt the emulator on an unexpected fall-through.&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:2"&gt;
&lt;p&gt;A mnemonic is the short text name for a CPU instruction (&lt;code&gt;rts&lt;/code&gt;, &lt;code&gt;jsr&lt;/code&gt;, &lt;code&gt;lda&lt;/code&gt;). The assembler turns each one into the matching machine-code byte.&amp;#160;&lt;a href="#fnref:2" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:3"&gt;
&lt;p&gt;The 65C816 addresses 24-bit memory split into 256 banks of 64KB. &lt;code&gt;$01:FF34&lt;/code&gt; reads &amp;ldquo;address &lt;code&gt;$FF34&lt;/code&gt; in bank &lt;code&gt;$01&lt;/code&gt;&amp;rdquo;. Code in different banks is reached with long-call instructions instead of short ones.&amp;#160;&lt;a href="#fnref:3" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:4"&gt;
&lt;p&gt;&lt;code&gt;jsr.l&lt;/code&gt; is a long jump to subroutine: it pushes a 24-bit return address (bank + 16-bit) before jumping. The matching return is &lt;code&gt;rtl&lt;/code&gt;. The short pair is &lt;code&gt;jsr&lt;/code&gt; + &lt;code&gt;rts&lt;/code&gt; (16-bit return). Mismatching them leaks or eats bytes off the stack.&amp;#160;&lt;a href="#fnref:4" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:5"&gt;
&lt;p&gt;Non-maskable interrupt. On the SNES it fires once per video frame, in the brief moment when the screen is between drawing two frames, and runs whatever handler the CPU is told to run for it.&amp;#160;&lt;a href="#fnref:5" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:6"&gt;
&lt;p&gt;The vector table is a fixed block at &lt;code&gt;$00FFE0&lt;/code&gt;-&lt;code&gt;$00FFFF&lt;/code&gt; listing handler addresses for reset, NMI, BRK, and others. Wiring a BRK trap means setting the BRK handler to halt the emulator and record where the bad instruction came from, so an unexpected &lt;code&gt;brk&lt;/code&gt; produces a debuggable crash instead of silent corruption.&amp;#160;&lt;a href="#fnref:6" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:7"&gt;
&lt;p&gt;The SNES CPU&amp;rsquo;s main RAM is 128KB. VRAM is the graphics chip&amp;rsquo;s video RAM (64KB), holding the tile data and the screen layout. Anything visible has to be in VRAM. Tile data is usually staged in main RAM and copied across in bulk between two drawn frames.&amp;#160;&lt;a href="#fnref:7" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</description></item><item><title>The complete machine, fading</title><link>https://man-you.ringum.net/posts/complete-machine/</link><pubDate>Sun, 03 May 2026 23:14:38 +0200</pubDate><guid>https://man-you.ringum.net/posts/complete-machine/</guid><description>&lt;p&gt;My iPod 5.5g is old enough to drink alcohol in any country that has a drinking age. I bought it used, swapped the battery and the disk for an SD card mod, and it still does the one thing it was sold to do: play music. No firmware update has ever taken a feature away. No company has decided the device is too old to deserve syncing. Apple, to their credit, still ships iTunes and Music with iPod sync on macOS. But even the day they stop, the iPod will keep playing whatever is already on the disk. The original purpose has not eroded.&lt;/p&gt;
&lt;p&gt;That is becoming rare.&lt;/p&gt;
&lt;h2 id="devices-die-from-the-network-now"&gt;Devices die from the network now&lt;/h2&gt;
&lt;p&gt;The hardware on my desk has not gotten worse. CPUs are faster, displays are sharper, batteries hold up longer than they used to. And yet the average lifespan of a &amp;ldquo;smart&amp;rdquo; thing I buy in 2026 feels shorter than what I was buying ten years ago. The thing breaks first from the network, not from the silicon.&lt;/p&gt;
&lt;p&gt;The pattern is familiar by now:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The companion app is removed from the store, or rewritten in a way that drops support for older models.&lt;/li&gt;
&lt;li&gt;The TLS certificate the device was pinned to expires, and the firmware that would let it accept a new one was never updated.&lt;/li&gt;
&lt;li&gt;The cloud API the device talks to is versioned out of existence. A &lt;code&gt;v1&lt;/code&gt; endpoint becomes &lt;code&gt;v2&lt;/code&gt;, and &lt;code&gt;v1&lt;/code&gt; is shut down because keeping it alive is unbillable maintenance.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A speaker, a thermostat, a camera, a &amp;ldquo;smart&amp;rdquo; anything. None of these failure modes have anything to do with whether the device can still do its job. They have to do with whether anyone, somewhere else, is still willing to keep the other end of the wire alive.&lt;/p&gt;
&lt;p&gt;AI accelerated the pattern without being its cause. Every product launch in the last two years has the same slide: &amp;ldquo;Powered by AI&amp;rdquo;, which usually means an API call to a cloud LLM the vendor pays for, the user does not control, and the headline feature does not work without. Cut the connection and the feature is gone. Often the basic feature is gone too, because the product team built the whole flow assuming the cloud would always answer.&lt;/p&gt;
&lt;p&gt;The same dynamic plays out at every floor. The vendor depends on the open source it ships on top of, and gets squeezed when upstream changes direction (&lt;a href="https://man-you.ringum.net/posts/supply-chain-control/"&gt;the supply-chain story&lt;/a&gt;). The engineer using AI tools depends on a token economy that is currently subsidised, and will get squeezed when investors want their money back (&lt;a href="https://man-you.ringum.net/posts/100x-engineer/"&gt;the 100x story&lt;/a&gt;). The buyer of the smart speaker depends on the vendor staying alive long enough to keep the cloud running. Three floors, one shape. Dependency on something you do not own is the constant. The label on the box changes.&lt;/p&gt;
&lt;h2 id="a-worse-mainframe-with-prettier-hardware"&gt;A worse mainframe, with prettier hardware&lt;/h2&gt;
&lt;p&gt;The easy version of this argument is &amp;ldquo;we are going back to the 70s mainframe.&amp;rdquo; It misses what got worse. The 70s deal was structurally better than what is shipping now in three specific places.&lt;/p&gt;
&lt;p&gt;The 70s mainframe sat in your building. You owned it, or your employer did, and the cable to the terminal ran through the walls of a building you had keys to. Today&amp;rsquo;s mainframe is offshore, owned by a company whose name you did not choose, reachable only over a TLS pipe whose certificates you do not control. The &amp;ldquo;central operator&amp;rdquo; is not down the hall. It is in someone else&amp;rsquo;s jurisdiction.&lt;/p&gt;
&lt;p&gt;The 70s terminal was honestly dumb. A keyboard, a screen, a serial line, no pretence. You knew what you had. Today&amp;rsquo;s terminal is shaped like a real computer and behaves like one until you cut its connection. The dishonesty of selling a thin-client as a personal device is the part that did not exist before.&lt;/p&gt;
&lt;p&gt;The 70s mainframe came with a service contract you signed. SLAs, support, a vendor who could be sued. Today&amp;rsquo;s &amp;ldquo;service&amp;rdquo; is a TOS the vendor can change weekly, with a clause letting them brick a device they do not like, and you signed it without reading because the alternative was that the box you paid for did not turn on.&lt;/p&gt;
&lt;p&gt;Three ways the deal got worse: location, honesty, accountability. The mainframe analogy understates the regression.&lt;/p&gt;
&lt;h2 id="what-complete-used-to-mean"&gt;What &amp;ldquo;complete&amp;rdquo; used to mean&lt;/h2&gt;
&lt;p&gt;You thought I was going to channel the Woz. Trying to be a bit more original.&lt;/p&gt;
&lt;p&gt;The Casio F-91W has been in continuous production since 1989. It tells the time, runs a stopwatch and an alarm, lights up when you press the button, and lasts about seven years on a battery. That is the entire feature set. There is no companion app. The watch will keep doing exactly what it was sold to do until the LCD fails or the case cracks, on a part nobody is going to deprecate, against a service nobody operates.&lt;/p&gt;
&lt;p&gt;A complete machine, in the sense I care about. Not &amp;ldquo;open&amp;rdquo; in the GPL sense, not &amp;ldquo;user-modifiable&amp;rdquo; in the kit-computer sense. Honest about what it is. The design refused to add things. Forty years later the refusal looks like prescience.&lt;/p&gt;
&lt;p&gt;The iPod 5.5g is the same kind of object on a shorter timeline. Music on the disk, codec in the firmware, click wheel and headphone jack with no server in the loop. If iTunes vanished tomorrow, syncing becomes annoying, but the device still plays. Communities are already keeping that path open: Rockbox, libimobiledevice, third-party sync tools. The world where you sync an iPod without Apple exists because the device is honest about what it does.&lt;/p&gt;
&lt;p&gt;A streaming-only speaker is the inverse. The codecs may live locally, but the catalogue does not, the auth does not, the discovery protocol does not. None of those parts belong to the buyer. The day any one of them goes away, the speaker stops being a speaker.&lt;/p&gt;
&lt;p&gt;The Casio and the iPod 5.5g are the floor the rest of the catalogue should be measured against. Most of what is shipping in 2026 fails the comparison and sits closer to the streaming speaker.&lt;/p&gt;
&lt;h2 id="linux-is-real-and-not-enough"&gt;Linux is real, and not enough&lt;/h2&gt;
&lt;p&gt;&amp;ldquo;Use Linux&amp;rdquo; is a real answer for laptops, servers, single-board computers, and a handful of phones. It is not an answer for the smart oven, the robot vacuum, the doorbell, or the infotainment system in your car. Most appliances ship as the vendor decided or they do not ship at all. The escape hatch exists. The long tail of household objects is outside it.&lt;/p&gt;
&lt;p&gt;Practical advice for non-technical buyers: pick devices whose core function does not depend on the cloud. Pick the dumb version when the dumb version still exists. &amp;ldquo;Smart&amp;rdquo; in 2026 mostly means &amp;ldquo;rented.&amp;rdquo;&lt;/p&gt;
&lt;h2 id="what-i-actually-do"&gt;What I actually do&lt;/h2&gt;
&lt;p&gt;I work on AI tooling and run cloud services for a living. The tension is real and I am inside it.&lt;/p&gt;
&lt;p&gt;On the personal side I lean local-first. &lt;a href="https://man-you.ringum.net/posts/higgins/"&gt;Higgins&lt;/a&gt; runs a 7B model on my laptop with a local SQLite. If the cloud disappears, the assistant still answers. Costs capability, gains independence. There is also a side benefit nobody prints on the box: running the thing yourself teaches you a thing or two about how it works. The cloud version abstracts away the parts you would otherwise be forced to understand. I value both more than I used to.&lt;/p&gt;
&lt;p&gt;The iPod will keep playing. The list of products I could say the same about in 2045 is shrinking every year.&lt;/p&gt;</description></item><item><title>Higgins: a neurosymbolic menubar buddy</title><link>https://man-you.ringum.net/posts/higgins/</link><pubDate>Mon, 20 Apr 2026 10:00:00 +0200</pubDate><guid>https://man-you.ringum.net/posts/higgins/</guid><description>&lt;p&gt;Higgins is a local assistant that lives in the menubar. A Qwen 2.5 7B model runs on-device via MLX, a SQLite triple store remembers things across sessions, and a small collection of tools — AppleScript runners, EventKit bridges, &lt;code&gt;gh&lt;/code&gt; CLI wrappers — lets him actually do work on the Mac. No cloud, no subscription, no data leaving the machine.&lt;/p&gt;
&lt;p&gt;The name is a nod to Magnum P.I.&amp;rsquo;s reserved English major-domo. The vibe was aspirational; most of the engineering effort went into making sure he doesn&amp;rsquo;t cheerfully hallucinate your dentist appointment into 2023.&lt;/p&gt;
&lt;h2 id="why-not-just-a-chat-wrapper"&gt;Why not just a chat wrapper&lt;/h2&gt;
&lt;p&gt;The starting pitch was neuro-symbolic: &amp;ldquo;LLMs are great at vibes and terrible at strict logic; symbolic systems are the opposite; put a logic engine inside a neural one.&amp;rdquo; Nice in a blog post, vague as a project.&lt;/p&gt;
&lt;p&gt;What it actually means in code: the neural side (Qwen) handles language, intent, paraphrasing. The symbolic side (SQLite facts table, typed tool schemas, EventKit queries) handles everything where being wrong is worse than being silent — dates, names, calendar events, PR numbers. The LLM never gets to compute a date from priors; it forwards your words to a parser. The LLM never claims to remember your dog&amp;rsquo;s name; the fact comes from a SQL row, or it doesn&amp;rsquo;t answer.&lt;/p&gt;
&lt;p&gt;That split is the whole project. Everything else is plumbing.&lt;/p&gt;
&lt;h2 id="the-qwen-brain"&gt;The Qwen brain&lt;/h2&gt;
&lt;p&gt;Qwen 2.5 7B Instruct, 4-bit quantized, ~4.5 GB on disk. Loaded via &lt;a href="https://github.com/ml-explore/mlx-swift-lm"&gt;mlx-swift-lm&lt;/a&gt; with progress reported through a &lt;code&gt;NSKeyValueObservation&lt;/code&gt; on the download&amp;rsquo;s &lt;code&gt;Progress&lt;/code&gt; object — since &lt;code&gt;swift-huggingface&lt;/code&gt; fires the handler once then mutates in place, not on every byte.&lt;/p&gt;
&lt;p&gt;Picking Qwen over Gemma was almost incidental. Gemma 3 4B worked but wouldn&amp;rsquo;t accept a &lt;code&gt;system&lt;/code&gt; role, forcing a hacky primer-pair trick. Gemma 4 26B-a4b is MoE, which mlx-swift-lm&amp;rsquo;s &lt;code&gt;Gemma4Model&lt;/code&gt; doesn&amp;rsquo;t implement yet. Qwen accepts system prompts cleanly and, crucially, was trained to emit native tool calls — which matters a lot more than benchmark scores.&lt;/p&gt;
&lt;p&gt;The old path was prompt-engineered: &lt;code&gt;TOOL_CALL toolname {...}&lt;/code&gt; in the output, parsed with regex. Fragile. Qwen wants to emit something like:&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;lt;tool_call&amp;gt;
{&amp;#34;name&amp;#34;: &amp;#34;reminder_add&amp;#34;, &amp;#34;arguments&amp;#34;: {&amp;#34;text&amp;#34;: &amp;#34;…&amp;#34;, &amp;#34;datetime&amp;#34;: &amp;#34;…&amp;#34;}}
&amp;lt;/tool_call&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/figure&gt;
&lt;p&gt;mlx-swift-lm surfaces this as &lt;code&gt;.toolCall(ToolCall)&lt;/code&gt; events in the generation stream, if you pass structured tool specs into &lt;code&gt;UserInput(chat:, tools:)&lt;/code&gt;. Once wired up, the model stops inventing its own format and starts using the one it was RL&amp;rsquo;d on.&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;figcaption&gt;Generation loop excerpt&lt;/figcaption&gt;
&lt;div class="highlight" title="Generation loop excerpt"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-swift" data-lang="swift"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;await&lt;/span&gt; &lt;span class="n"&gt;gen&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="n"&gt;MLXLMCommon&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;generate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;parameters&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="n"&gt;gen&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;s&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="n"&gt;output&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;toolCall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;c&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="n"&gt;call&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;break&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;The text parser stuck around as a fallback for edge cases.&lt;/p&gt;
&lt;h2 id="the-symbolic-side"&gt;The symbolic side&lt;/h2&gt;
&lt;p&gt;A SQLite file at &lt;code&gt;~/Library/Application Support/Symbolic/memory.db&lt;/code&gt; with two tables:&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;TABLE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;episodes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;INTEGER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;PRIMARY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;user_msg&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;TEXT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;assistant_reply&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;TEXT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;signal&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;INTEGER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ts&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;REAL&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;TABLE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;facts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;INTEGER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;PRIMARY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;subject&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;TEXT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;predicate&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;TEXT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;object&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;TEXT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;confidence&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;REAL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;last_seen&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;REAL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;UNIQUE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;predicate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;object&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;Episodes are the raw log: every chat turn lands here. Facts are the distilled, queryable form — subject/predicate/object triples with a confidence score. The interesting work happens in the pipe between the two.&lt;/p&gt;
&lt;h2 id="sleep"&gt;Sleep&lt;/h2&gt;
&lt;p&gt;The consolidation metaphor is cribbed directly from human memory: episodic hippocampal traces don&amp;rsquo;t become semantic knowledge until you sleep. Higgins does the same. After 15 minutes of chat silence, or when you tap the 🌙 button, he runs a pass:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;NREM&lt;/strong&gt;: the model reads the day&amp;rsquo;s recent episodes and emits one JSON object per line — extracted stable facts about you, your world, your preferences. Ephemeral conversational filler (greetings, acknowledgments, weather chat) is skipped.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Synaptic downscaling&lt;/strong&gt;: every fact&amp;rsquo;s confidence is multiplied by 0.95. Unused memories decay. Recalled ones get re-bumped above 1.0 and capped.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Purge&lt;/strong&gt;: facts below 0.05 confidence are dropped.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Dream log&lt;/strong&gt;: a human-readable markdown summary lands in &lt;code&gt;~/Library/Application Support/Symbolic/dreams/YYYY-MM-DD.md&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;figure class="code-block-figure"&gt;
&lt;figcaption&gt;Sleeper entrypoint&lt;/figcaption&gt;
&lt;div class="highlight" title="Sleeper entrypoint"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-swift" data-lang="swift"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;nightCycle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;episodeLimit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Int&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;async&lt;/span&gt; &lt;span class="kr"&gt;throws&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Report&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;episodes&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="n"&gt;await&lt;/span&gt; &lt;span class="n"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;recentEpisodes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;episodeLimit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;facts&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;await&lt;/span&gt; &lt;span class="n"&gt;extractFacts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;from&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;episodes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;facts&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="n"&gt;await&lt;/span&gt; &lt;span class="n"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;addFact&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;predicate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;predicate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;object&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;object&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="n"&gt;await&lt;/span&gt; &lt;span class="n"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;decayFacts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;multiplier&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.95&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;purged&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="n"&gt;await&lt;/span&gt; &lt;span class="n"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;purgeWeakFacts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;threshold&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.05&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;There&amp;rsquo;s no &lt;code&gt;remember&lt;/code&gt; command to learn. Facts accumulate while you use the thing.&lt;/p&gt;
&lt;h2 id="per-turn-recall-closing-the-loop"&gt;Per-turn recall: closing the loop&lt;/h2&gt;
&lt;p&gt;Sleep would be useless if the facts just piled up. Before each generation, the user&amp;rsquo;s query is keyword-matched against the &lt;code&gt;facts&lt;/code&gt; table (top 5 hits, &lt;code&gt;LIKE&lt;/code&gt; on subject/predicate/object). Matching facts are injected into &lt;em&gt;that turn&amp;rsquo;s&lt;/em&gt; system context as a bracketed prefix:&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;[Known facts from memory:
- dog / name / Rex (confidence 0.95)
- manz / employer / Woosmap (confidence 0.87)
]
what&amp;#39;s my dog&amp;#39;s name?&lt;/code&gt;&lt;/pre&gt;
&lt;/figure&gt;
&lt;p&gt;History stays clean — the decoration only exists for the single inference call. No tool call needed for the model to answer &amp;ldquo;Rex&amp;rdquo;. The symbolic substrate grounds the neural output on the way past.&lt;/p&gt;
&lt;p&gt;At low scale (hundreds of facts) &lt;code&gt;LIKE&lt;/code&gt; matching is good enough. Embedding-backed similarity search is the upgrade when it isn&amp;rsquo;t.&lt;/p&gt;
&lt;h2 id="tools"&gt;Tools&lt;/h2&gt;
&lt;p&gt;The tool layer is where the project actually earns its keep. Three surfaces:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;AppleScript library.&lt;/strong&gt; Curated scripts live in &lt;code&gt;~/Library/Application Support/Symbolic/scripts/&lt;/code&gt;, each with frontmatter metadata (&lt;code&gt;-- name:&lt;/code&gt;, &lt;code&gt;-- description:&lt;/code&gt;, &lt;code&gt;-- args:&lt;/code&gt;). Seed scripts ship with the app; the user can edit or add more. The model calls them by name — &lt;code&gt;calendar_today&lt;/code&gt;, &lt;code&gt;note_quick&lt;/code&gt;, etc. — as if they were first-class tools.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;EventKit bridge.&lt;/strong&gt; AppleScript against Calendar.app is infamously slow — iterating every event on every calendar through the bridge takes 30+ seconds for a busy calendar. EventKit queries the same data in milliseconds. The catch: it needs &lt;code&gt;NSCalendarsFullAccessUsageDescription&lt;/code&gt; in a real &lt;code&gt;Info.plist&lt;/code&gt;. The SPM executable embeds one via a linker trick:&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;figcaption&gt;Package.swift&lt;/figcaption&gt;
&lt;div class="highlight" title="Package.swift"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-swift" data-lang="swift"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;linkerSettings&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;unsafeFlags&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s"&gt;&amp;#34;-Xlinker&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;-sectcreate&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s"&gt;&amp;#34;-Xlinker&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;__TEXT&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s"&gt;&amp;#34;-Xlinker&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;__info_plist&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s"&gt;&amp;#34;-Xlinker&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;Resources/Info.plist&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;]),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;&lt;code&gt;-sectcreate __TEXT __info_plist&lt;/code&gt; injects the plist into a section TCC reads when deciding whether to prompt for permission. Works without wrapping the binary in a &lt;code&gt;.app&lt;/code&gt; bundle.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;gh&lt;/code&gt; CLI wrappers.&lt;/strong&gt; &lt;code&gt;gh_my_prs&lt;/code&gt;, &lt;code&gt;gh_review_queue&lt;/code&gt;, &lt;code&gt;gh_notifications&lt;/code&gt;, &lt;code&gt;gh_recent_merged&lt;/code&gt;. Subprocess calls to the user&amp;rsquo;s existing &lt;code&gt;gh&lt;/code&gt; session — no OAuth dance, no token storage. A &lt;code&gt;ProcessRunner&lt;/code&gt; actor handles timeouts and respects &lt;code&gt;Task.isCancelled&lt;/code&gt; so a stuck subprocess doesn&amp;rsquo;t freeze the UI when you hit the red stop button.&lt;/p&gt;
&lt;p&gt;Tool results render as folded cards with a wrench icon, tool name, and a one-line summary. Click to expand. Errors auto-expand. &lt;code&gt;#123 · PR title · owner/repo&lt;/code&gt; links are real anchors thanks to &lt;a href="https://github.com/gonzalezreal/swift-markdown-ui"&gt;MarkdownUI&lt;/a&gt; on the assistant-side rendering.&lt;/p&gt;
&lt;h2 id="dates-or-why-the-model-doesnt-do-arithmetic"&gt;Dates, or: why the model doesn&amp;rsquo;t do arithmetic&lt;/h2&gt;
&lt;p&gt;7B models are bad at date math. &amp;ldquo;Remind me tomorrow at 8:15&amp;rdquo; reliably produced ISO strings from 2023, 2024, anywhere but &lt;em&gt;actually tomorrow&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;The fix is to not let the model do arithmetic at all. Any tool argument named &lt;code&gt;datetime&lt;/code&gt;, &lt;code&gt;when&lt;/code&gt;, &lt;code&gt;due_date&lt;/code&gt;, etc. gets intercepted in Swift before dispatch. A tiny table handles English and French relative words (&lt;code&gt;tomorrow&lt;/code&gt;, &lt;code&gt;today&lt;/code&gt;, &lt;code&gt;demain&lt;/code&gt;, &lt;code&gt;hier&lt;/code&gt;) — &lt;code&gt;NSDataDetector&lt;/code&gt; isn&amp;rsquo;t locale-parameterizable and picks up the system locale, which fails on English keywords under a French macOS. Anything the table doesn&amp;rsquo;t catch falls through to &lt;code&gt;NSDataDetector&lt;/code&gt;, which anchors relative phrasing against &lt;code&gt;Date()&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Critically, the user&amp;rsquo;s raw message is the source of truth, not the model&amp;rsquo;s output. If you typed &amp;ldquo;tomorrow&amp;rdquo;, &amp;ldquo;tomorrow&amp;rdquo; is what gets parsed — even if the model hallucinates an ISO. The model&amp;rsquo;s job is intent extraction, not date reasoning.&lt;/p&gt;
&lt;h2 id="the-applescript-run-button"&gt;The AppleScript Run button&lt;/h2&gt;
&lt;p&gt;When the model writes code in an assistant reply, the code block renders with a language tag and a copy button. When the language is &lt;code&gt;applescript&lt;/code&gt;, a Run button appears:&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;figcaption&gt;CodeBlock with conditional run action&lt;/figcaption&gt;
&lt;div class="highlight" title="CodeBlock with conditional run action"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-swift" data-lang="swift"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;MarkdownUI&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Markdown&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;turn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;markdownBlockStyle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;codeBlock&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;CodeBlock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;language&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;language&lt;/span&gt; &lt;span class="p"&gt;??&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;onRun&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;runAction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;language&lt;/span&gt; &lt;span class="p"&gt;??&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;The action shells out to &lt;code&gt;osascript -e &amp;lt;script&amp;gt;&lt;/code&gt;, captures stdout, appends the result as a tool turn. The user stays in charge — nothing runs without the click. Safer than giving the model unrestricted automation tools, more flexible than the curated script library.&lt;/p&gt;
&lt;h2 id="what-grew-out-of-it"&gt;What grew out of it&lt;/h2&gt;
&lt;p&gt;The original plan was to fine-tune a custom voice — a &amp;ldquo;genius caveman&amp;rdquo; Grug, all short fragments. Hours of LoRA experiments on Gemma 2 2B, Gemma 3 4B, Gemma 4 E4B later, the honest conclusion: voice tuning on small models produces caricature or collapse, and nobody cares about voice when the tools don&amp;rsquo;t work. The pivot was embracing stock Qwen and pouring the effort into the symbolic substrate instead.&lt;/p&gt;
&lt;p&gt;What&amp;rsquo;s there now:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;On-device Qwen 2.5 7B&lt;/strong&gt; with native tool calling, loading with proper progress feedback&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Conversation persistence&lt;/strong&gt; as JSONL (machine) + Markdown sidecar (human), auto-saved per turn, restored on launch if recent&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tool calls&lt;/strong&gt; through Qwen&amp;rsquo;s native format, fallback text parser, per-tool timeouts, cancel button, pending spinner&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Sleep/nap consolidation&lt;/strong&gt; turning episodes into facts, confidence decay, dream logs&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Per-turn recall&lt;/strong&gt; grounding generation on stored facts without touching history&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;EventKit&lt;/strong&gt; for calendar/reminders in milliseconds, &lt;code&gt;gh&lt;/code&gt; CLI for GitHub, NSDataDetector for natural-language dates&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Right-click menu&lt;/strong&gt;: new conversation, sleep now, open dreams/conversations/memory in Finder&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Markdown-rendered assistant replies&lt;/strong&gt; via MarkdownUI, plain monospaced tool output&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;AppleScript Run button&lt;/strong&gt; on any &lt;code&gt;applescript&lt;/code&gt; code block in a reply&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The fine-tune loop (REM-derived training candidates → periodic LoRA refresh of stable voice and high-confidence facts) is designed but deferred. Retrieval has a faster iteration loop; weights-baking earns its keep when retrieval stops being enough. Given Higgins has had exactly zero users for three days, that threshold is a while off.&lt;/p&gt;
&lt;p&gt;Meanwhile he tells me what&amp;rsquo;s due tomorrow, doesn&amp;rsquo;t make up dates, remembers the cat&amp;rsquo;s name is Charlie, and opens PR review queues when I ask. Good enough for now.&lt;/p&gt;</description></item><item><title>Freddie part 2: from tiles to map</title><link>https://man-you.ringum.net/posts/freddie-part2/</link><pubDate>Tue, 24 Mar 2026 12:00:00 +0100</pubDate><guid>https://man-you.ringum.net/posts/freddie-part2/</guid><description>&lt;p&gt;In &lt;a href="https://man-you.ringum.net/posts/scaling-freddie-10m/"&gt;part 1&lt;/a&gt; I covered the hard problems: COG multi-resolution, SDF raster smoothing, and Barbapapa topology-preserving Laplacian smoothing. Freddie could generate good-looking 10m land cover vector tiles. This post is about what happened next: killing the algorithm I spent a week perfecting, solving a surprisingly dumb storage problem, and wiring the tiles all the way through to the map SDK.&lt;/p&gt;
&lt;h2 id="barbapapa-is-dead"&gt;Barbapapa is dead&lt;/h2&gt;
&lt;p&gt;I killed Barbapapa a few weeks after shipping it.&lt;/p&gt;
&lt;p&gt;It stung a little. Seven revisions, the hole vertex bug that ate two days, the Jacobi-style simultaneous updates, the deterministic iteration fix. Elegant stuff. But after staring at enough tiles side by side, the conclusion was unavoidable: Barbapapa was lying.&lt;/p&gt;
&lt;p&gt;Land cover at 10m resolution doesn&amp;rsquo;t have smooth boundaries. A forest edge is where a satellite sensor switched from &amp;ldquo;tree&amp;rdquo; to &amp;ldquo;grass&amp;rdquo; at some pixel boundary. It&amp;rsquo;s inherently jagged. Barbapapa was manufacturing gentle curves that were never in the data: aesthetically pleasing, but dishonest. The tiles looked better in the way a retouched photo looks better. Not more accurate, just smoother.&lt;/p&gt;
&lt;p&gt;Meanwhile, the chain-based Douglas-Peucker simplifier I&amp;rsquo;d built alongside it was doing more useful work with less ceremony.&lt;/p&gt;
&lt;h2 id="edge-dp"&gt;Edge DP&lt;/h2&gt;
&lt;p&gt;Edge DP operates on the same half-edge topology that Barbapapa used, but the philosophy is opposite. Instead of moving vertices closer to their neighbors to manufacture curves, it removes the vertices that don&amp;rsquo;t carry information. Less romantic, more honest.&lt;/p&gt;
&lt;p&gt;The core idea: find the topologically significant vertices (junctions where three or more land cover classes meet) and treat everything between them as a chain. Each chain can be simplified independently without breaking the topology.&lt;/p&gt;
&lt;p&gt;Five phases:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1. Find junctions.&lt;/strong&gt; Walk every vertex and count how many distinct classes touch it. Three or more classes makes it a junction, an anchor in the topology skeleton. Tile boundary vertices are also pinned.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. Extract chains.&lt;/strong&gt; Walk each polygon ring, breaking at junctions and class transitions. Two adjacent faces sharing a boundary reference the same chain object. Simplify it once, both faces see the result. This is the same principle that made Barbapapa work (shared topology) but applied to vertex removal instead of vertex movement.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3. Douglas-Peucker per chain.&lt;/strong&gt; The textbook recursive algorithm: find the vertex farthest from the line between endpoints, keep it if it exceeds the threshold, recurse on both sides. Nothing clever here. The cleverness is in applying it at chain granularity instead of per-polygon, where it would tear shared boundaries apart.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;4. Reconcile.&lt;/strong&gt; If any chain needs a vertex, it stays. Prevents holes at multi-chain junctions where one chain would remove a vertex another one depends on.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;5. Rebuild twins.&lt;/strong&gt; Remove the marked vertices from all rings and relink half-edge twin pointers so the topology stays consistent.&lt;/p&gt;
&lt;p&gt;The result: aggressive vertex reduction with guaranteed topological consistency. No gaps, no overlaps, no invented curves. What you see is what the sensor saw, with fewer vertices saying it.&lt;/p&gt;
&lt;h3 id="z14factor"&gt;Z14Factor&lt;/h3&gt;
&lt;p&gt;One subtlety at high zoom: SDF upscaling leaves residual staircase artifacts. The Gaussian blur approximates class boundaries but doesn&amp;rsquo;t eliminate the pixel grid entirely. A &lt;code&gt;Z14Factor&lt;/code&gt; of 0.6 multiplies the DP threshold at z14 and above, a &lt;em&gt;less&lt;/em&gt; aggressive simplification that preserves more of the SDF-smoothed detail. Base threshold of 2.0 becomes 1.2 at z14, keeping vertices that would otherwise be culled. The SDF already did the heavy lifting on boundary shape. DP just needs to not undo that work. Small tweak, noticeable difference.&lt;/p&gt;
&lt;h3 id="what-got-deleted"&gt;What got deleted&lt;/h3&gt;
&lt;p&gt;This was the satisfying part. With Edge DP handling everything alone, I got to delete a lot of code:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Barbapapa: gone (~6,800 lines including tests and docs)&lt;/li&gt;
&lt;li&gt;Visvalingam-Whyatt: gone (experimental, Edge DP outperformed it everywhere)&lt;/li&gt;
&lt;li&gt;SimplifyFaces: gone (subsumed by Edge DP&amp;rsquo;s chain extraction)&lt;/li&gt;
&lt;li&gt;Ten per-class viewer layers: collapsed into one fill layer with zoom-interpolated colors&lt;/li&gt;
&lt;li&gt;Duplicated config across &lt;code&gt;freddie&lt;/code&gt; and &lt;code&gt;freddie-server&lt;/code&gt;: unified into a single &lt;code&gt;ProcessingConfig&lt;/code&gt; struct&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The pipeline description shrank to one line: &lt;code&gt;SDF (blur=6.0, minZ=10) + mode filter 7×7 + edge DP (threshold=2.0)&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="painters-algorithm"&gt;Painter&amp;rsquo;s algorithm&lt;/h2&gt;
&lt;p&gt;Collapsing ten per-class layers into one fill layer immediately broke rendering. Enclosed faces could paint over their parents. A lake surrounded by forest would disappear under green because the forest polygon rendered last. Obvious in hindsight, invisible until you actually try it.&lt;/p&gt;
&lt;p&gt;The fix: topological sorting before MVT encoding. For each face, ray-cast to check whether it&amp;rsquo;s enclosed by another face, then depth-first sort so parents paint first. A lake inside a forest inside a continent renders in the right order. Combined with placing the landcover layer right after the map background in the style, one MapLibre fill layer handles everything correctly. Fewer layers, less complexity, better result.&lt;/p&gt;
&lt;h2 id="the-200gb-problem"&gt;The 200GB problem&lt;/h2&gt;
&lt;p&gt;Freddie generates beautiful tiles. But beautiful tiles sitting on my laptop don&amp;rsquo;t help anyone. They need to end up in the actual map, served alongside the base tiles: roads, buildings, POIs, all the stuff Planetiler generates.&lt;/p&gt;
&lt;p&gt;The obvious approach: merge the two mbtiles files offline into one. Base map (~80GB) + landcover (~60GB) = one file, ship it. I built &lt;code&gt;freddie-merge&lt;/code&gt; for exactly this. Iterated over every tile in both files, combined the MVT layers, wrote the result. Clean, simple, done.&lt;/p&gt;
&lt;p&gt;The merged output was over 200GB. I stared at the number for a while.&lt;/p&gt;
&lt;p&gt;The reason is embarrassing. Planetiler stores identical tiles once through content-addressed deduplication. Vast stretches of ocean at low zoom are the same tile: one copy, millions of references. Merging landcover into every tile makes each one unique, so deduplication collapses entirely. 80 + 60 doesn&amp;rsquo;t equal 200, but 80 (heavily deduplicated) + 60 (heavily deduplicated) merged together becomes 200 (nothing deduplicated). I&amp;rsquo;d undone one of Planetiler&amp;rsquo;s best optimizations without realizing it.&lt;/p&gt;
&lt;h2 id="runtime-mvt-concatenation"&gt;Runtime MVT concatenation&lt;/h2&gt;
&lt;p&gt;The fix is almost too simple to be satisfying.&lt;/p&gt;
&lt;p&gt;MVT layers are top-level protobuf fields. Two valid MVT byte streams concatenated together produce a valid MVT byte stream. No parsing, no reencoding, no schema reconciliation, just append bytes. I had to read the protobuf spec twice to convince myself this was actually legal.&lt;/p&gt;
&lt;p&gt;So: don&amp;rsquo;t merge at build time. Keep both tile pyramids as separate mbtiles files and concatenate at read time:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Fetch base tile from mbtiles A&lt;/li&gt;
&lt;li&gt;Fetch landcover tile from mbtiles B&lt;/li&gt;
&lt;li&gt;Decompress both (gzip)&lt;/li&gt;
&lt;li&gt;Concatenate, landcover first, so it paints behind everything&lt;/li&gt;
&lt;li&gt;Recompress, serve&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;When there&amp;rsquo;s no landcover configured, the merge path is never entered. Zero overhead for the existing setup. Both files keep their original deduplication intact. And &lt;code&gt;freddie-merge&lt;/code&gt; (the tool I built, debugged, optimized with ATTACH JOIN, rewrote to iterate the smaller set) joins the growing pile of deleted code.&lt;/p&gt;
&lt;p&gt;Layer ordering matters: landcover sits right after the map background, before roads and buildings and labels. At high zoom where ESA WorldCover runs out of detail, the existing OSM &lt;code&gt;landcover_wood&lt;/code&gt; and &lt;code&gt;landcover_grass&lt;/code&gt; layers take over with a minzoom of 12.&lt;/p&gt;
&lt;h2 id="teaching-the-sdk-about-fill-colors"&gt;Teaching the SDK about fill colors&lt;/h2&gt;
&lt;p&gt;The last piece of the puzzle was the rendering SDK. It already had a multiclass styling system for POIs: one MapLibre layer that renders different icons and text labels depending on the feature class. I naively assumed landcover would slot right in. Same system, different data, right?&lt;/p&gt;
&lt;p&gt;No. POIs and landcover need fundamentally different MapLibre expression shapes, and the difference is annoyingly subtle.&lt;/p&gt;
&lt;p&gt;A POI layer uses &lt;code&gt;case&lt;/code&gt; at the root to pick an icon, with &lt;code&gt;interpolate&lt;/code&gt; nested inside for zoom-dependent sizing:&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;case&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;==&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;get&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;class&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;park&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;park-icon&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;==&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;get&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;class&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;school&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;school-icon&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;default-icon&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;A landcover fill layer needs the structure inverted: &lt;code&gt;interpolate&lt;/code&gt; at the root for smooth color transitions across zoom, with &lt;code&gt;case&lt;/code&gt; nested inside each zoom stop to pick the per-class color:&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;interpolate&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;linear&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;zoom&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;case&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;==&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;get&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;class&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;wood&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;#1a3d1a&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;==&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;get&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;class&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;grass&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;#2d5a1e&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;transparent&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="mi"&gt;14&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;case&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;==&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;get&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;class&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;wood&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;#2d6b2d&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;==&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;get&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;class&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;grass&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;#4a8c3f&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;transparent&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;The inversion matters because MapLibre can&amp;rsquo;t interpolate between case results. You have to case-select within each interpolation stop. I spent an embarrassing amount of time trying the other way before reading the spec carefully enough to understand why.&lt;/p&gt;
&lt;p&gt;A new &lt;code&gt;ClassMetadata&lt;/code&gt; type carries zoom color stops instead of flat color strings, and the expression builder constructs the full &lt;code&gt;interpolate&lt;/code&gt;+&lt;code&gt;case&lt;/code&gt; tree from the metadata. The POI path stays untouched, just extracted into its own methods so the two flows don&amp;rsquo;t tangle. One of those changes where the diff is medium-sized but the head-scratching that preceded it was substantial.&lt;/p&gt;
&lt;h2 id="four-repos-one-pipeline"&gt;Four repos, one pipeline&lt;/h2&gt;
&lt;p&gt;Stepping back, the whole story spans four repositories and a surprising amount of deleted code:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;freddie&lt;/strong&gt; generates 10m land cover vector tiles: SDF smoothing, Edge DP simplification, painter&amp;rsquo;s algorithm sorting, mbtiles output&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;data-pipeline&lt;/strong&gt; spins up a beefy ARM instance, downloads ~100GB of ESA WorldCover source TIFFs from S3, runs freddie, uploads the result, self-destructs&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;maps&lt;/strong&gt; serves tiles with runtime MVT concatenation: no offline merge, no deduplication loss, zero overhead when landcover isn&amp;rsquo;t configured&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;maps-js&lt;/strong&gt; renders them with zoom-interpolated fill colors through the multiclass style system&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;From satellite raster to styled vector on screen. GeoTIFF in, mbtiles out, MVT over HTTP, MapLibre expressions in the style JSON. Each piece does one thing.&lt;/p&gt;
&lt;p&gt;The part that surprised me most wasn&amp;rsquo;t any individual algorithm. It was how cleanly everything composed once each piece had well-defined inputs and outputs. Freddie doesn&amp;rsquo;t know about the map SDK. The map server doesn&amp;rsquo;t know about SDF smoothing. The SDK doesn&amp;rsquo;t know the tiles came from satellite imagery. They just agree on protobuf bytes and the rest takes care of itself.&lt;/p&gt;
&lt;p&gt;Also: I wrote and then deleted an entire smoothing algorithm, an entire merge tool, and three simplification strategies. The codebase got smaller as the feature got bigger. That might be the most satisfying part of this whole project.&lt;/p&gt;</description></item><item><title>The most useless way to port a macOS app</title><link>https://man-you.ringum.net/posts/clone-desktop/</link><pubDate>Tue, 24 Mar 2026 09:30:00 +0100</pubDate><guid>https://man-you.ringum.net/posts/clone-desktop/</guid><description>&lt;p&gt;I grew up fascinated by projects like GNUStep, Haiku, Etoile, Wine, and ReactOS. Engineering feats, all of them. They reverse-engineer or reimplement entire operating system APIs so that software written for one platform can run on another. And they almost always end up in the same place: impressive technically, starved for contributors, forever chasing a moving target they can never quite catch.&lt;/p&gt;
&lt;p&gt;I never liked the state of the Linux desktop either. Not because it&amp;rsquo;s bad per se, but because it&amp;rsquo;s fragmented. A KDE app on GNOME looks alien. Firefox rolls its own everything. GTK and Qt will never agree on anything. Every toolkit draws its own widgets, manages its own text rendering, handles its own accessibility story. The result is a desktop that feels like a coalition of independent projects rather than a coherent system.&lt;/p&gt;
&lt;p&gt;This project is not going to fix any of that. But it&amp;rsquo;s an interesting story about how a combination of prior work, Apple open-sourcing key components, and an AI pair programmer led me down a rabbit hole I didn&amp;rsquo;t plan to enter.&lt;/p&gt;
&lt;h2 id="the-prior-art-that-made-this-possible"&gt;The prior art that made this possible&lt;/h2&gt;
&lt;p&gt;I&amp;rsquo;ve been working with wgpu (Rust&amp;rsquo;s WebGPU implementation) for a while now, building a &lt;a href="https://man-you.ringum.net/backroom/woosmap-tiles/"&gt;map renderer&lt;/a&gt; for Woosmap. That project taught me the fundamentals: how to manage GPU pipelines, how to do instanced rendering, how to deal with text atlases and glyph rasterization, how to bridge Rust and Swift through UniFFI.&lt;/p&gt;
&lt;p&gt;On the Swift side, I had two apps: &lt;strong&gt;Tunes&lt;/strong&gt;, a music player, and &lt;strong&gt;Leela&lt;/strong&gt;, an internal management tool for Woosmap services. Both are SwiftUI apps, both depend on Apple&amp;rsquo;s frameworks in the usual way.&lt;/p&gt;
&lt;p&gt;So one evening, half-curious and half-joking, I fed Claude the source of those projects alongside some context about GNUStep and friends, and typed:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&amp;ldquo;What would it take to make Tunes build and run on Linux?&amp;rdquo;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;And then things got out of control.&lt;/p&gt;
&lt;h2 id="what-claude-came-back-with"&gt;What Claude came back with&lt;/h2&gt;
&lt;p&gt;The answer was, predictably, &amp;ldquo;a lot.&amp;rdquo; But the interesting part was the breakdown. SwiftUI is the main dependency, and SwiftUI is closed-source. But Swift itself is open-source. Swift Foundation is open-source. The Swift Package Manager works on Linux. So the gap is really:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;A SwiftUI implementation (the view hierarchy, state management, layout engine, modifiers)&lt;/li&gt;
&lt;li&gt;Some AppKit shims (NSColor, NSAppearance, the bits that SwiftUI still leans on)&lt;/li&gt;
&lt;li&gt;A rendering backend that isn&amp;rsquo;t Core Animation or Metal&lt;/li&gt;
&lt;li&gt;A compositor to manage windows, menus, and the desktop chrome&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Instead of stopping at &amp;ldquo;that&amp;rsquo;s insane, don&amp;rsquo;t do it,&amp;rdquo; I kept going. Claude kept going. We started building.&lt;/p&gt;
&lt;h2 id="architecture"&gt;Architecture&lt;/h2&gt;
&lt;p&gt;The project (codenamed &lt;strong&gt;Clone&lt;/strong&gt;) splits pretty naturally along a language boundary.&lt;/p&gt;
&lt;p&gt;Rust does what Rust is good at: GPU work. A wgpu renderer with pipelines for rectangles, rounded rects (SDF-based, because I can&amp;rsquo;t stop using SDFs apparently), shadows, text via a glyph atlas, and wallpapers. It also runs the window compositor through winit.&lt;/p&gt;
&lt;p&gt;Swift does what Swift is good at: UI. A from-scratch SwiftUI implementation (about 50 View types, &lt;code&gt;@State&lt;/code&gt;/&lt;code&gt;@Binding&lt;/code&gt;/&lt;code&gt;@Environment&lt;/code&gt;, &lt;code&gt;ViewBuilder&lt;/code&gt;, layout, modifiers), the whole declarative stack. Plus enough AppKit shims to keep real apps happy, a SwiftData reimplementation backed by SQLite, and an IPC protocol over Unix sockets.&lt;/p&gt;
&lt;p&gt;UniFFI bridges the two. Each frame, Rust asks Swift for render commands, Swift resolves the view tree into a flat list of positioned primitives, and Rust batches them into instanced GPU draws. It&amp;rsquo;s the same bridge I use in the map renderer, so at least that part wasn&amp;rsquo;t new territory.&lt;/p&gt;
&lt;pre class="mermaid"&gt;graph LR
A["App.body"] --&gt; B["ViewBuilder"] --&gt; C["_resolve()"] --&gt; D["Layout"]
D --&gt; E["CommandFlattener"] --&gt; F["IPC
CGFloat → Float"] --&gt; G["Rust batcher"] --&gt; H["wgpu draws"]
style A fill:#c4a7e7,stroke:#6e6a86,color:#191724
style B fill:#c4a7e7,stroke:#6e6a86,color:#191724
style C fill:#c4a7e7,stroke:#6e6a86,color:#191724
style D fill:#c4a7e7,stroke:#6e6a86,color:#191724
style E fill:#f6c177,stroke:#6e6a86,color:#191724
style F fill:#f6c177,stroke:#6e6a86,color:#191724
style G fill:#9ccfd8,stroke:#6e6a86,color:#191724
style H fill:#9ccfd8,stroke:#6e6a86,color:#191724
&lt;/pre&gt;
&lt;h2 id="first-signs-of-life"&gt;First signs of life&lt;/h2&gt;
&lt;p&gt;The moment I&amp;rsquo;ll remember is the grey rectangle. &amp;ldquo;Clone Desktop&amp;rdquo; in the center, a dock at the bottom with colored squares. I&amp;rsquo;d been at it for hours, wrestling with UniFFI bindings and layout math, and suddenly there it was: pixels on screen, drawn by Swift code, pushed through a Rust GPU backend, on something that was decidedly not macOS.&lt;/p&gt;
&lt;p&gt;&lt;figure&gt;
&lt;img
src="https://man-you.ringum.net/posts/clone-desktop/clone-1_hu_60da9db4b46e573c.webp"
srcset="https://man-you.ringum.net/posts/clone-desktop/clone-1_hu_60da9db4b46e573c.webp 960w, https://man-you.ringum.net/posts/clone-desktop/clone-1_hu_cd2b29062a903b83.webp 2784w"
sizes="(max-width: 960px) 100vw, 960px"
alt="The first Clone Desktop render: a grey surface, centered label, and a color-swatch dock"
width="960"
height="651"
loading="lazy"
decoding="async"
/&gt;
&lt;figcaption&gt;Day one: a grey box, a label, and a dock. It&amp;#39;s not much, but it compiles and renders.&lt;/figcaption&gt;
&lt;/figure&gt;&lt;/p&gt;
&lt;h2 id="settings-and-finder"&gt;Settings and Finder&lt;/h2&gt;
&lt;p&gt;Once the basic views worked (stacks, text, lists, navigation), I needed something real to throw at them. Settings was a natural first target: sidebars, forms, toggles, text fields, the kind of layout variety that breaks things fast. Finder came next because browsing a directory with &lt;code&gt;List&lt;/code&gt; and &lt;code&gt;ForEach&lt;/code&gt; is such a fundamental SwiftUI pattern that if it didn&amp;rsquo;t work, nothing would.&lt;/p&gt;
&lt;p&gt;&lt;figure&gt;
&lt;img
src="https://man-you.ringum.net/posts/clone-desktop/clone-2_hu_c535fac1e06a428f.webp"
srcset="https://man-you.ringum.net/posts/clone-desktop/clone-2_hu_c535fac1e06a428f.webp 960w, https://man-you.ringum.net/posts/clone-desktop/clone-2_hu_d543a0938420ea6e.webp 2784w"
sizes="(max-width: 960px) 100vw, 960px"
alt="Clone Desktop running Settings (Wi-Fi panel) and Finder side by side, dark mode"
width="960"
height="651"
loading="lazy"
decoding="async"
/&gt;
&lt;figcaption&gt;Settings showing Wi-Fi preferences alongside Finder browsing the home directory. Both are SwiftUI apps running through Clone&amp;#39;s stack.&lt;/figcaption&gt;
&lt;/figure&gt;&lt;/p&gt;
&lt;p&gt;Dark mode just kind of happened. Once I shimmed &lt;code&gt;NSAppearance&lt;/code&gt; and implemented the semantic color system (&lt;code&gt;Color.primary&lt;/code&gt;, &lt;code&gt;.secondary&lt;/code&gt;, the system grays), flipping between light and dark was a toggle. A small thing, but unreasonably satisfying: it made the whole experiment feel like a real desktop for the first time.&lt;/p&gt;
&lt;p&gt;&lt;figure&gt;
&lt;img
src="https://man-you.ringum.net/posts/clone-desktop/clone-3_hu_7656abf32f2c59d8.webp"
srcset="https://man-you.ringum.net/posts/clone-desktop/clone-3_hu_7656abf32f2c59d8.webp 960w, https://man-you.ringum.net/posts/clone-desktop/clone-3_hu_477ea4514a69efe6.webp 2784w"
sizes="(max-width: 960px) 100vw, 960px"
alt="Settings showing the General panel in light mode"
width="960"
height="651"
loading="lazy"
decoding="async"
/&gt;
&lt;figcaption&gt;The same Settings app in light mode. Appearance switching works through the reimplemented NSAppearance and Color system.&lt;/figcaption&gt;
&lt;/figure&gt;&lt;/p&gt;
&lt;h2 id="tunes"&gt;Tunes&lt;/h2&gt;
&lt;p&gt;This was the whole point, remember? &amp;ldquo;What would it take to make Tunes build and run on Linux?&amp;rdquo; Well, it builds. The login sheet renders inside the app window rather than as a separate &lt;code&gt;NSPanel&lt;/code&gt; (a compromise, but not a terrible one), and the SwiftUI code is essentially unchanged. &lt;code&gt;TextField&lt;/code&gt;, &lt;code&gt;SecureField&lt;/code&gt;, &lt;code&gt;Toggle&lt;/code&gt;, &lt;code&gt;Button&lt;/code&gt;, &lt;code&gt;Link&lt;/code&gt;: they all resolve and render. Same source, different universe.&lt;/p&gt;
&lt;p&gt;&lt;figure&gt;
&lt;img
src="https://man-you.ringum.net/posts/clone-desktop/clone-4_hu_5ff44dcb7735661f.webp"
srcset="https://man-you.ringum.net/posts/clone-desktop/clone-4_hu_5ff44dcb7735661f.webp 960w, https://man-you.ringum.net/posts/clone-desktop/clone-4_hu_b08f4d867416f64c.webp 2784w"
sizes="(max-width: 960px) 100vw, 960px"
alt="Tunes showing a login form with username/password fields, a toggle, and a registration link, alongside a Finder window"
width="960"
height="651"
loading="lazy"
decoding="async"
/&gt;
&lt;figcaption&gt;Tunes running its login flow through Clone. The sheet renders in-window rather than as a separate panel, one of many compromises.&lt;/figcaption&gt;
&lt;/figure&gt;&lt;/p&gt;
&lt;h2 id="leela"&gt;Leela&lt;/h2&gt;
&lt;p&gt;Leela is a management dashboard for Woosmap services: tabs, lists, nested navigation, version selectors, deploy queues. Most of the UI is driven by API responses, so it hammers &lt;code&gt;ForEach&lt;/code&gt; with dynamic data, &lt;code&gt;@StateObject&lt;/code&gt;, and conditional rendering. If Settings was a stroll through the park, Leela was a stress test.&lt;/p&gt;
&lt;p&gt;&lt;figure&gt;
&lt;img
src="https://man-you.ringum.net/posts/clone-desktop/clone-5_hu_2145acbb1b59b75f.webp"
srcset="https://man-you.ringum.net/posts/clone-desktop/clone-5_hu_2145acbb1b59b75f.webp 960w, https://man-you.ringum.net/posts/clone-desktop/clone-5_hu_a5c8020ac9f97b4f.webp 2784w"
sizes="(max-width: 960px) 100vw, 960px"
alt="Leela showing a service management view with sidebar navigation, tab bar, version tags, and service listings"
width="960"
height="651"
loading="lazy"
decoding="async"
/&gt;
&lt;figcaption&gt;Leela&amp;#39;s services view running through Clone. Tabs, lists, version badges, sidebar navigation, all SwiftUI, all reimplemented.&lt;/figcaption&gt;
&lt;/figure&gt;&lt;/p&gt;
&lt;p&gt;I won&amp;rsquo;t pretend it was smooth. Getting here meant implementing a surprising amount of SwiftUI&amp;rsquo;s surface area. The state management system alone (&lt;code&gt;StateGraph&lt;/code&gt;, scoped identity for &lt;code&gt;ForEach&lt;/code&gt;, call-index disambiguation) went through several iterations where everything would render once and then silently stop updating. The kind of bug where you stare at a diff for an hour before realizing a closure captured a copy instead of a reference.&lt;/p&gt;
&lt;h2 id="under-the-hood"&gt;Under the hood&lt;/h2&gt;
&lt;h3 id="text-rendering"&gt;Text rendering&lt;/h3&gt;
&lt;p&gt;Text goes through cosmic-text for shaping, then gets rasterized into a 4096x4096 glyph atlas (single-channel, R8). Each glyph is cached and rendered as an instanced GPU quad. Fonts are bundled: Inter for UI text, Phosphor for icons.&lt;/p&gt;
&lt;p&gt;This is almost certainly not how Apple does it. A real system would share GPU textures between the compositor and app, or pull from a system font cache. But it works, and there&amp;rsquo;s a special joy in watching a glyph atlas fill up character by character as the UI renders for the first time.&lt;/p&gt;
&lt;h3 id="layout"&gt;Layout&lt;/h3&gt;
&lt;p&gt;I reverse-engineered SwiftUI&amp;rsquo;s layout by reading &lt;a href="https://www.objc.io/books/thinking-in-swiftui/"&gt;objc.io&amp;rsquo;s&lt;/a&gt; thinking-in-swiftui and a lot of trial and error. The model: parents propose a size to children, children report back what they need, parents position them. Sounds simple until &lt;code&gt;ZStack&lt;/code&gt; enters the picture: nil-sized views from &lt;code&gt;.background()&lt;/code&gt; modifiers would expand to fill constraints and eat space from real siblings. That one took a while.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;ScrollView&lt;/code&gt; was another head-scratcher: it fills its proposed size but lays out content unbounded in the scroll axis. Getting that inversion right, where the container constrains in one direction and is infinite in the other, broke my mental model twice before clicking.&lt;/p&gt;
&lt;h3 id="ipc"&gt;IPC&lt;/h3&gt;
&lt;p&gt;Each app is a separate process. The compositor (CloneDesktop) runs the Rust event loop and manages surfaces. Apps connect over a Unix socket at &lt;code&gt;/tmp/clone-compositor.sock&lt;/code&gt; and exchange length-prefixed JSON messages. There&amp;rsquo;s an annoying &lt;code&gt;CGFloat&lt;/code&gt;-to-&lt;code&gt;Float&lt;/code&gt; conversion at the wire boundary: Swift thinks in 64-bit coordinates, the GPU thinks in &lt;code&gt;f32&lt;/code&gt;, and someone has to reconcile that at the border.&lt;/p&gt;
&lt;h3 id="the-ycodebuild-trick"&gt;The &lt;code&gt;ycodebuild&lt;/code&gt; trick&lt;/h3&gt;
&lt;p&gt;This is probably my favorite hack in the project. To compile an existing macOS app against Clone instead of Apple&amp;rsquo;s frameworks, &lt;code&gt;ycodebuild&lt;/code&gt; generates a shadow SPM package that maps &lt;code&gt;import SwiftUI&lt;/code&gt; to Clone&amp;rsquo;s SwiftUI, &lt;code&gt;import AppKit&lt;/code&gt; to Clone&amp;rsquo;s shims, and so on. The app source code doesn&amp;rsquo;t change at all: you just build against a different package graph, and the compiled binary talks to the compositor over the socket. The same &lt;code&gt;.swift&lt;/code&gt; file, two completely different platforms.&lt;/p&gt;
&lt;h2 id="honest-assessment"&gt;Honest assessment&lt;/h2&gt;
&lt;p&gt;I should be upfront about the gap between &amp;ldquo;it renders&amp;rdquo; and &amp;ldquo;it works.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;Animations are mostly stubbed. Accessibility is nonexistent. The compositor redraws every surface every frame like it&amp;rsquo;s 1997. There are &lt;code&gt;// TODO: implement&lt;/code&gt; scattered across the codebase where AppKit APIs return no-ops and hope nobody notices. Sheets render in-window because I never built the panel system. The glyph atlas will fall over the moment someone opens a CJK document.&lt;/p&gt;
&lt;p&gt;And (this is the awkward part) it doesn&amp;rsquo;t actually run on Linux yet. The whole premise was &amp;ldquo;what would it take,&amp;rdquo; and the answer turned out to include some Apple framework dependencies I haven&amp;rsquo;t replaced with their open-source equivalents. It&amp;rsquo;s doable. It&amp;rsquo;s just not done.&lt;/p&gt;
&lt;p&gt;But here&amp;rsquo;s the thing that caught me off guard. The time from that initial Claude prompt to a state where Tunes and Leela compile and render recognizable UI was hours, not weeks. Not autonomous AI magic (plenty of manual fixes and architectural decisions along the way) but Claude carried an enormous amount of boilerplate: generating 50 View type stubs, wiring up modifier chains, implementing the state graph, setting up IPC. The kind of work that would&amp;rsquo;ve taken me days of tedious typing, compressed into a conversation.&lt;/p&gt;
&lt;h2 id="why-this-matters-a-little"&gt;Why this matters (a little)&lt;/h2&gt;
&lt;p&gt;Apple open-sourcing Swift and Foundation was a bigger deal than most people realize. Not because anyone&amp;rsquo;s going to ship a SwiftUI app on Linux tomorrow, but because it lowered the floor for experiments like this from &amp;ldquo;completely impossible&amp;rdquo; to &amp;ldquo;merely impractical.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;The projects I admired growing up (GNUStep, Haiku, Wine) were built by small teams reverse-engineering closed systems over years. The combination of open-source language infrastructure and AI assistance compresses that timeline dramatically. Not to a point where it&amp;rsquo;s practical or production-ready, but to a point where a single person can explore the shape of the problem in a weekend.&lt;/p&gt;
&lt;p&gt;That&amp;rsquo;s the real takeaway. Not &amp;ldquo;I ported macOS to Linux.&amp;rdquo; I didn&amp;rsquo;t. But I went from a throwaway prompt to colored boxes on screen running real SwiftUI app code, and that felt like something worth writing about.&lt;/p&gt;
&lt;h2 id="whats-next"&gt;What&amp;rsquo;s next&lt;/h2&gt;
&lt;p&gt;Honestly? Probably nothing. The project scratched a twenty-year itch. But I know myself, and I know there&amp;rsquo;s a Kawase blur pipeline sitting unused in the renderer that&amp;rsquo;s going to call my name at 11pm some Tuesday. If I do keep going:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Per-window offscreen textures&lt;/strong&gt;: the compositor shouldn&amp;rsquo;t redraw everything every frame, that&amp;rsquo;s embarrassing&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Glassmorphism / backdrop blur&lt;/strong&gt;: because what&amp;rsquo;s the point of reimplementing macOS if you can&amp;rsquo;t have the frosted glass&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Actually running on Linux&lt;/strong&gt;: the whole original premise, still unfinished&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Shared GPU textures&lt;/strong&gt;: how a real compositor would work, instead of copying pixels through a socket like an animal&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Or maybe it stays as a weekend experiment and a blog post. Either way, the kid who thought GNUStep was the coolest thing ever is pretty happy right now.&lt;/p&gt;</description></item><item><title>When "mostly aligned" stops being enough</title><link>https://man-you.ringum.net/posts/supply-chain-control/</link><pubDate>Sat, 07 Mar 2026 22:41:00 +0100</pubDate><guid>https://man-you.ringum.net/posts/supply-chain-control/</guid><description>&lt;p&gt;Every product starts by depending on open source it doesn&amp;rsquo;t fully understand. That&amp;rsquo;s fine. It&amp;rsquo;s the whole point. You stand on the shoulders of people who solved problems you haven&amp;rsquo;t even encountered yet, and you ship. The dependency is invisible because it works.&lt;/p&gt;
&lt;p&gt;Then the product grows, and you start noticing the edges. The upstream project makes choices you wouldn&amp;rsquo;t have made. The schema carries fields you don&amp;rsquo;t use. The routing engine takes a turn you can&amp;rsquo;t explain. The update process becomes a gamble: bump to the latest version and hope nothing changed in a way that breaks your use case. You&amp;rsquo;re accountable to your customers for software you treat as a black box.&lt;/p&gt;
&lt;p&gt;This is the story of how that played out for us across map tiles, routing, and rendering, and why the hardest part isn&amp;rsquo;t the engineering.&lt;/p&gt;
&lt;h2 id="not-all-dependencies-are-equal"&gt;Not all dependencies are equal&lt;/h2&gt;
&lt;p&gt;We depend on dozens of open source projects. FastAPI, Django, Pydantic, httpx, pytest, the list goes on. If FastAPI disappeared tomorrow, we&amp;rsquo;d rewrite some decorators and route definitions. It would be work, but the product would still be the product. These are plumbing. They shape the surface (how requests arrive, how responses leave) but they don&amp;rsquo;t define what the product &lt;em&gt;is&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Map tiles and routing are different. Without tiles, the maps SDK renders nothing. Without a routing engine, the distance API returns nothing. These aren&amp;rsquo;t tools we use to build the product. They &lt;em&gt;are&lt;/em&gt; the product, or at least the foundation it stands on. An empty shell without them.&lt;/p&gt;
&lt;p&gt;The dependency risk isn&amp;rsquo;t about how many lines of code use a library. It&amp;rsquo;s about how much of the product&amp;rsquo;s core value lives in upstream code you don&amp;rsquo;t control. When that answer is &amp;ldquo;most of it,&amp;rdquo; the relationship with that upstream project stops being a convenience and starts being an existential question.&lt;/p&gt;
&lt;h2 id="the-illusion"&gt;The illusion&lt;/h2&gt;
&lt;p&gt;Here&amp;rsquo;s the part nobody talks about.&lt;/p&gt;
&lt;p&gt;When you ship an MVP built on third-party foundations, the customer doesn&amp;rsquo;t know. They don&amp;rsquo;t know the tiles come from MapTiler. They don&amp;rsquo;t know the routing runs on Valhalla. They see a Woosmap maps API and a Woosmap distance API. They think they bought a product.&lt;/p&gt;
&lt;p&gt;And they did, from their perspective. The API works. The tiles render. The routes compute. The fact that most of the core value is someone else&amp;rsquo;s software, running in your infrastructure, with your branding on top, is an implementation detail they never see.&lt;/p&gt;
&lt;p&gt;So when you sell the MVP, you&amp;rsquo;re implicitly selling the product three years from now. The customer is buying what they think exists. Leadership is reporting on what they think the team built. And the engineering team knows the truth: that the product, as everyone imagines it, doesn&amp;rsquo;t exist yet. What exists is a thin layer on top of open source that happens to work.&lt;/p&gt;
&lt;p&gt;This creates a brutal dynamic. When you invest in actually building the foundations (replacing vendor tiles with your own schema, understanding the routing engine you depend on, building a style compiler) it looks like you&amp;rsquo;re doing nothing. No new features. No visible progress. The maps still render. The routes still compute. From the outside, nothing changed.&lt;/p&gt;
&lt;p&gt;Worse, the transition introduces blips. You&amp;rsquo;re replacing running systems with new ones you wrote, and you&amp;rsquo;re human, so mistakes happen. A regression in tile rendering. A routing edge case the old system handled that yours doesn&amp;rsquo;t yet. For a brief window, you&amp;rsquo;ve made things slightly worse in pursuit of making them fundamentally better. And nobody outside the team has the context to understand why.&lt;/p&gt;
&lt;p&gt;You&amp;rsquo;re in a fight against everyone&amp;rsquo;s perception: customers, leadership, sometimes other engineering teams. Living up to what people think the product already does while quietly building the thing they assumed was there all along.&lt;/p&gt;
&lt;h2 id="the-pattern"&gt;The pattern&lt;/h2&gt;
&lt;p&gt;It starts the same way every time.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Phase 1: it works.&lt;/strong&gt; You adopt the open source project. It does 90% of what you need. The remaining 10% you work around. The trade-off is obvious and good: building from scratch would take months, this takes days. You ship.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Phase 2: it mostly works.&lt;/strong&gt; The product grows. The 10% gap becomes visible to customers. You can&amp;rsquo;t change it because it&amp;rsquo;s upstream. You file issues, maybe contribute patches, but the project&amp;rsquo;s priorities aren&amp;rsquo;t your priorities. They&amp;rsquo;re building for a community. You&amp;rsquo;re building for a product.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Phase 3: you&amp;rsquo;re accountable.&lt;/strong&gt; A customer reports a bug that lives in the upstream code. You can&amp;rsquo;t just say &amp;ldquo;that&amp;rsquo;s an open source issue.&amp;rdquo; It&amp;rsquo;s your product. You need to fix it, but fixing it means understanding a codebase that wasn&amp;rsquo;t written for you, by people who had different goals. The gap between &amp;ldquo;using&amp;rdquo; and &amp;ldquo;maintaining&amp;rdquo; becomes real.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Phase 4: the fork in the road.&lt;/strong&gt; You have three options. Contribute upstream and hope the community accepts your direction. Fork and maintain your own version. Or rebuild the parts you need from scratch.&lt;/p&gt;
&lt;h2 id="map-tiles-from-vendor-to-vertical-integration"&gt;Map tiles: from vendor to vertical integration&lt;/h2&gt;
&lt;h3 id="buying-tiles"&gt;Buying tiles&lt;/h3&gt;
&lt;p&gt;The first version of Woosmap Maps didn&amp;rsquo;t generate tiles at all. We licensed vector tiles from MapTiler, a flat yearly fee for an MBTiles pyramid. Predictable, simple. We focused entirely on the SDK: the Mapbox GL fork, the Google Maps compatibility layer, the service clients. The tiles were someone else&amp;rsquo;s problem.&lt;/p&gt;
&lt;p&gt;This was the right call. We were building a maps product, not a geodata pipeline. MapTiler tiles worked, the OpenMapTiles schema was well-documented, and we could ship without learning anything about OSM data processing.&lt;/p&gt;
&lt;p&gt;By the time we started thinking about generating tiles ourselves, the pricing model had changed. I don&amp;rsquo;t remember the exact structure anymore, but it was enough of a signal: the terms we&amp;rsquo;d signed up for weren&amp;rsquo;t the terms going forward. Beyond pricing, tiles carried 16 layers with attributes we never read, the update cadence wasn&amp;rsquo;t ours, and the OpenMapTiles CC-BY 4.0 license required visible attribution, which was friction in every enterprise sales conversation.&lt;/p&gt;
&lt;h3 id="running-openmaptiles-ourselves"&gt;Running OpenMapTiles ourselves&lt;/h3&gt;
&lt;p&gt;We took the OpenMapTiles stack in-house. PostgreSQL-based: import OSM data with imposm3 into PostGIS, run generated SQL scripts to transform it layer by layer, then generate vector tiles through per-tile PostgreSQL queries. Same schema, our infrastructure.&lt;/p&gt;
&lt;p&gt;It worked, but planet generation took days and the database needed hundreds of gigabytes. And we still had the same schema, the same unused attributes, the same attribution requirements. We&amp;rsquo;d replaced a vendor bill with an infrastructure bill while keeping every constraint.&lt;/p&gt;
&lt;h3 id="planetiler"&gt;Planetiler&lt;/h3&gt;
&lt;p&gt;Planetiler (originally Flatmap) changed the equation. Java, streaming processing, no database. A full planet in hours on a single machine instead of days on a database cluster. We ran it with the &lt;code&gt;planetiler-openmaptiles&lt;/code&gt; profile: same output, fraction of the cost.&lt;/p&gt;
&lt;p&gt;But we still had the schema problem. Faster generation of tiles you don&amp;rsquo;t control is still tiles you don&amp;rsquo;t control.&lt;/p&gt;
&lt;h3 id="clean-room-schema"&gt;Clean-room schema&lt;/h3&gt;
&lt;p&gt;The real break was writing our own &lt;a href="https://man-you.ringum.net/backroom/woosmap-tiles/"&gt;Planetiler profile from scratch&lt;/a&gt;. Not a fork of OpenMapTiles. A clean-room reimplementation with our own layer names, our own attributes, and nothing we don&amp;rsquo;t use.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;transportation&lt;/code&gt; became &lt;code&gt;roads&lt;/code&gt;. &lt;code&gt;transportation_name&lt;/code&gt; became &lt;code&gt;road_labels&lt;/code&gt;. &lt;code&gt;housenumber&lt;/code&gt; got dropped entirely. Almost every layer is written in Kotlin because the logic is too specific for declarative YAML: transit network propagation from OSM relations, building height arithmetic, multi-script labels across 85 languages, roads pre-split by bridge/tunnel structure.&lt;/p&gt;
&lt;h3 id="the-style-compiler"&gt;The style compiler&lt;/h3&gt;
&lt;p&gt;Owning the tile schema without owning the style is half the job. A production Mapbox GL style is thousands of lines of JSON. Roads alone need six variants each across eight road classes.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://man-you.ringum.net/posts/map-style-compiler/"&gt;Elzar&lt;/a&gt;, our style compiler, generates that JSON from Python code. The tile schema and the style are designed in lockstep. When the Kotlin tile generator emits a &lt;code&gt;hide_3d&lt;/code&gt; attribute on buildings, the style compiler references it in the same PR. Schema and style can&amp;rsquo;t drift because they live in the same repo.&lt;/p&gt;
&lt;h3 id="the-spec-question"&gt;The spec question&lt;/h3&gt;
&lt;p&gt;Once you own both the tiles and the style generation, a new question surfaces: does the Mapbox GL style spec itself actually fit what you&amp;rsquo;re building?&lt;/p&gt;
&lt;p&gt;The spec was designed for a general-purpose renderer serving a general-purpose schema. It has no concept of road structure, so a single road class needs six style layers (tunnel casing, tunnel fill, road casing, road fill, bridge casing, bridge fill) because brunnel is just another filter attribute. Eight road classes times six variants is 48 layers just for roads, before labels and shields. It&amp;rsquo;s like using assembly to build a web server: technically possible, but is it reasonable? Complex logic gets encoded as JSON expression trees (nested arrays of &lt;code&gt;[&amp;quot;case&amp;quot;, [&amp;quot;all&amp;quot;, [&amp;quot;has&amp;quot;, &amp;quot;name:nonlatin&amp;quot;], ...]]&lt;/code&gt;) that are technically correct and practically unreadable.&lt;/p&gt;
&lt;p&gt;Elzar makes this manageable. Define a road once, get six variants. Use &lt;code&gt;~Q(hide_3d__exists=True)&lt;/code&gt; instead of raw filter arrays. Pre-splitting roads into &lt;code&gt;roads_tunnel&lt;/code&gt;, &lt;code&gt;roads&lt;/code&gt;, and &lt;code&gt;roads_bridge&lt;/code&gt; at the tile level was our way of optimizing &lt;em&gt;around&lt;/em&gt; the spec, moving work from render time to tile generation time because the style format couldn&amp;rsquo;t express it efficiently.&lt;/p&gt;
&lt;p&gt;Elzar would stay regardless of the output format. Even if we designed a custom style spec, we&amp;rsquo;d still want to define styles in Python (something executable, debuggable, testable) rather than editing a declarative format by hand. The compiler&amp;rsquo;s value isn&amp;rsquo;t papering over the Mapbox spec. It&amp;rsquo;s that Python is a better authoring language for styles than any JSON dialect ever will be. The compiler stays. Only its target changes.&lt;/p&gt;
&lt;h3 id="the-renderer"&gt;The renderer&lt;/h3&gt;
&lt;p&gt;That&amp;rsquo;s where &lt;a href="https://man-you.ringum.net/posts/gpu-map-renderer/"&gt;Nimbus&lt;/a&gt;, the GPU map renderer experiment in Rust, enters the picture. If you control the renderer, you control the style format. The Mapbox spec stops being a constraint and becomes a choice. You can support it for compatibility while designing something better for the pipeline you actually have.&lt;/p&gt;
&lt;p&gt;A style format designed for our schema wouldn&amp;rsquo;t need six layers per road class. It would know about brunnel structure because the tiles already express it. Tiles that carry exactly the data the style describes. A style format that maps naturally to the tile schema. A renderer that understands both natively. And Elzar at the center, generating whatever output the renderer needs from the same Python definitions we already have.&lt;/p&gt;
&lt;h2 id="routing-the-black-box-problem"&gt;Routing: the black box problem&lt;/h2&gt;
&lt;h3 id="osrm"&gt;OSRM&lt;/h3&gt;
&lt;p&gt;The distance API started on OSRM. It&amp;rsquo;s fast and well-proven. But it loads the entire road graph into RAM, and for a world-scale service, that&amp;rsquo;s a hard constraint. The memory footprint made it impractical for the coverage we needed.&lt;/p&gt;
&lt;h3 id="valhalla"&gt;Valhalla&lt;/h3&gt;
&lt;p&gt;Valhalla solved the memory problem. Tile-based graph storage means you can serve the world without loading it all into RAM. We migrated, it worked, and for a while the &amp;ldquo;mostly aligned&amp;rdquo; phase was comfortable.&lt;/p&gt;
&lt;p&gt;Then we started hitting the edges.&lt;/p&gt;
&lt;p&gt;Valhalla is a massive C++ codebase maintained by a community with its own priorities. When a customer reports a routing bug (a wrong turn cost, a route through a service road that should be avoided) we can&amp;rsquo;t just fix it. We&amp;rsquo;d need to understand why the cost model works the way it does, trace through code that wasn&amp;rsquo;t written for our use case, and either patch it locally or convince the upstream community that our fix is the right one.&lt;/p&gt;
&lt;p&gt;The tile update process is where the black box problem gets painful. When we want to regenerate Valhalla tiles with fresh OSM data, we typically also bump Valhalla to the latest version, because the version that generates tiles and the version that serves them sometimes need to match. But a version bump means inheriting every change the community made since our last update. New cost model tweaks we didn&amp;rsquo;t ask for. Behavioral changes we don&amp;rsquo;t understand. Route quality regressions we discover from customer complaints, not from changelogs.&lt;/p&gt;
&lt;p&gt;We&amp;rsquo;re accountable for this software. Customers don&amp;rsquo;t care that Valhalla is open source. They see a Woosmap API returning a bad route and they file a ticket with us. And we&amp;rsquo;re stuck between &amp;ldquo;we don&amp;rsquo;t understand why it does that&amp;rdquo; and &amp;ldquo;we can&amp;rsquo;t change it without forking a C++ project we don&amp;rsquo;t deeply know.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;The C++ ecosystem makes this harder. Build systems, dependency management, debugging tooling: none of it is as accessible as the JVM or Rust&amp;rsquo;s cargo. Taking deeper ownership of Valhalla isn&amp;rsquo;t just a knowledge problem, it&amp;rsquo;s an ergonomics problem.&lt;/p&gt;
&lt;h3 id="calculon"&gt;Calculon&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://man-you.ringum.net/posts/calculon/"&gt;Calculon&lt;/a&gt; is the &amp;ldquo;what if we built it ourselves&amp;rdquo; experiment: a routing engine in Rust. Bidirectional A*, many-to-many distance matrices, driving/cycling/pedestrian profiles, Valhalla-compatible API. It reuses Valhalla&amp;rsquo;s tile format (designing a graph format from scratch would be its own project) but everything else is new code.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s not a Valhalla replacement yet. But when a cost model produces a weird route, we can read the code. When we want to change how turn penalties work, we change them. When we bump a dependency, it&amp;rsquo;s a Rust crate with a changelog we understand, not a C++ monolith where any subsystem might have shifted.&lt;/p&gt;
&lt;p&gt;There&amp;rsquo;s a middle path too: taking deeper ownership of Valhalla itself, contributing changes that serve our use case. But that requires investing heavily in a C++ codebase whose architecture and community may or may not align with where we need to go.&lt;/p&gt;
&lt;h2 id="when-replacing-a-black-box-makes-things-better"&gt;When replacing a black box makes things better&lt;/h2&gt;
&lt;p&gt;The pattern isn&amp;rsquo;t always painful. Sometimes replacing a dependency you don&amp;rsquo;t control with something you wrote yourself turns out to be a straight upgrade.&lt;/p&gt;
&lt;p&gt;The Woosmap Store Locator used Mapnik (a C++ rendering library) to generate map tiles showing store locations. Mapnik is a serious piece of software, battle-tested for raster map rendering. But it wasn&amp;rsquo;t designed for our use case: rendering MVT vector tiles with point data. It had become a dependency we couldn&amp;rsquo;t easily update, couldn&amp;rsquo;t debug when it misbehaved, and couldn&amp;rsquo;t adapt to our evolving needs.&lt;/p&gt;
&lt;p&gt;We &lt;a href="https://github.com/Woosmap/stores/pull/815"&gt;replaced it&lt;/a&gt; with a Python renderer built on Cairo. The new renderer was simpler: written in a language the whole team knows, doing exactly what we need and nothing more. And it turned out to be more performant. Less load on the database. Enough of an improvement that we could scale down the RDS instance.&lt;/p&gt;
&lt;p&gt;A full-featured C++ rendering engine, replaced by a focused Python implementation, and the result was &lt;em&gt;faster&lt;/em&gt; and &lt;em&gt;cheaper&lt;/em&gt;. Not because Python is faster than C++. Obviously it isn&amp;rsquo;t. But because a tool built for your specific problem, that you understand completely, can be optimized in ways a general-purpose tool never will be. You know which queries to run, which data to skip, which shortcuts are safe. The black box can&amp;rsquo;t know any of that.&lt;/p&gt;
&lt;h2 id="the-ones-still-early-in-the-cycle"&gt;The ones still early in the cycle&lt;/h2&gt;
&lt;p&gt;Not every dependency has reached the accountability phase yet. &lt;a href="https://man-you.ringum.net/backroom/static_maps/"&gt;Maparazzo&lt;/a&gt;, our static maps renderer, wraps Mapbox GL Native&amp;rsquo;s C++ core in a Python library for headless map image generation. Right now it&amp;rsquo;s in phase 1: it works. The C++ renderer produces correct images, the Python wrapper makes it callable from our FastAPI services, and static maps ship to customers.&lt;/p&gt;
&lt;p&gt;But the signs are already there. The C++ renderer leaks memory: OpenGL contexts hold state after objects are destroyed. We&amp;rsquo;ve worked around it by pooling &lt;code&gt;Map&lt;/code&gt; instances and reusing GL contexts, but the leak is in code we inherited, not code we wrote. When it misbehaves, debugging means diving into a massive C++ codebase with its own conventions, its own build system, its own history of decisions we weren&amp;rsquo;t part of.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s comfortable enough today. The problems will come. And when they do, Nimbus (which can already render our tiles headlessly from Rust) is the path out. Same pattern, different timeline.&lt;/p&gt;
&lt;h2 id="the-open-source-dependency-lifecycle"&gt;The open source dependency lifecycle&lt;/h2&gt;
&lt;p&gt;The uncomfortable truth is that depending on open source unknowingly makes you a maintainer. Not in the &amp;ldquo;you have commit access&amp;rdquo; sense. In the &amp;ldquo;you&amp;rsquo;re responsible for its behavior in your product&amp;rdquo; sense.&lt;/p&gt;
&lt;p&gt;For an MVP, &amp;ldquo;mostly aligned&amp;rdquo; is enough. The open source project does 90% of what you need, the community is active, the abstractions hold. You&amp;rsquo;d be foolish to build from scratch. And the community&amp;rsquo;s goals don&amp;rsquo;t need to perfectly match yours. They just need to overlap enough that you can ship.&lt;/p&gt;
&lt;p&gt;But products have customers. Customers have expectations. When the open source project&amp;rsquo;s goals diverge from yours, even slightly, you feel it. Not as a single breaking change, but as accumulated friction. Features you can&amp;rsquo;t add because the schema doesn&amp;rsquo;t support them. Bugs you can&amp;rsquo;t fix because the codebase is impenetrable. Updates you can&amp;rsquo;t skip because the format requires version parity.&lt;/p&gt;
&lt;p&gt;And ownership doesn&amp;rsquo;t stop at the obvious dependencies. Once you own the data and the generation, you discover that the &lt;em&gt;formats&lt;/em&gt; and &lt;em&gt;specs&lt;/em&gt; are dependencies too. The Mapbox style spec. Valhalla&amp;rsquo;s tile format. These are constraints you inherited from the ecosystem, and they shape what&amp;rsquo;s easy and what&amp;rsquo;s hard in ways that aren&amp;rsquo;t visible until you try to push past them.&lt;/p&gt;
&lt;p&gt;The question isn&amp;rsquo;t whether to take ownership. It&amp;rsquo;s when, of what, and how deep. Do the changes you need benefit the community? Would they accept a PR, or is your direction orthogonal to theirs? Is the ecosystem accessible enough to invest in, or is the cost of understanding it higher than the cost of rebuilding?&lt;/p&gt;
&lt;p&gt;For tiles, we kept Planetiler as the engine and own everything else: schema, style, and the compiler that connects them. For routing, the answer is still forming. For the style format and the renderer, we can work around the constraints today, but we can see the path to not having to.&lt;/p&gt;
&lt;h2 id="we-have-no-vision"&gt;&amp;ldquo;We have no vision&amp;rdquo;&lt;/h2&gt;
&lt;p&gt;I&amp;rsquo;ve heard this from engineers on the team. That we&amp;rsquo;re only reactive. That we just respond to customer requests and there&amp;rsquo;s no plan. And I understand why it looks that way: if you think phase 1 is the end, then everything after looks like firefighting.&lt;/p&gt;
&lt;p&gt;But if you lay out the phases, the vision is obvious. We have years of deliberate work ahead: owning the tile schema, closing the style spec gap, making the renderer a first-class citizen, taking control of routing. Each phase builds on the last. Each one makes the product more ours and less a wrapper around someone else&amp;rsquo;s decisions. That&amp;rsquo;s not reactive. That&amp;rsquo;s a roadmap.&lt;/p&gt;
&lt;p&gt;The problem is that this roadmap is invisible to anyone who thinks the product was finished when the MVP shipped. And that&amp;rsquo;s the majority of people: customers, leadership, and yes, most engineers. They see the API, it works, so the hard part must be done. Everything after is maintenance.&lt;/p&gt;
&lt;p&gt;This is exactly the same problem as technical debt, just at a different scale. Everyone will sign up for taking on debt if they think it ships something faster. Add a dependency you don&amp;rsquo;t understand. Skip the tests. Use the upstream schema as-is. Ship it. Move on to the next thing, take on more debt there too. And when someone proposes paying it down (replacing Mapnik with our own renderer, writing a clean-room tile schema, building a style compiler) the response is &amp;ldquo;why? it works.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;It works until it doesn&amp;rsquo;t. And by then the debt has compounded. The codebase is half-rotten, everyone complains about it, but nobody wants to stop adding features long enough to fix it. The boy scout rule (leave the code better than you found it) gets dismissed as slowing things down. So the rot spreads.&lt;/p&gt;
&lt;p&gt;Supply chain debt works the same way. Every dependency you don&amp;rsquo;t understand is a loan against your future ability to move fast. The interest is paid in debugging time, in workarounds, in version bumps that break things you can&amp;rsquo;t explain, in customers waiting for fixes that live in upstream code you can&amp;rsquo;t change. And just like financial debt, the longer you ignore it, the more it costs.&lt;/p&gt;
&lt;p&gt;The vision isn&amp;rsquo;t a feature roadmap. It&amp;rsquo;s the systematic transfer of ownership from upstream to us, one layer at a time, while the product keeps running. It&amp;rsquo;s unglamorous, often invisible, and it&amp;rsquo;s the only way to build something that lasts.&lt;/p&gt;
&lt;p&gt;My role as CTO is to grow the technological asset of the company. Features are what the product &lt;em&gt;does&lt;/em&gt;. The asset is what the product &lt;em&gt;is&lt;/em&gt;. The PO grows the former. I grow the latter. When the asset is a thin wrapper around upstream dependencies, it&amp;rsquo;s fragile. Anyone can replicate it. Every phase of ownership makes it thicker, harder to reproduce, more valuable. That&amp;rsquo;s not maintenance. That&amp;rsquo;s building the company&amp;rsquo;s core value.&lt;/p&gt;
&lt;p&gt;And that means making these phases crystal clear to everyone. If the team doesn&amp;rsquo;t see the multi-phase roadmap, they&amp;rsquo;ll assume there isn&amp;rsquo;t one. If leadership doesn&amp;rsquo;t understand why replacing a working renderer with a new one matters, they&amp;rsquo;ll see wasted effort. If engineers think phase 1 is the destination, they&amp;rsquo;ll resist every investment in phase 2 through 5 as unnecessary complexity.&lt;/p&gt;
&lt;p&gt;And here&amp;rsquo;s where I haven&amp;rsquo;t done a good enough job. When the foundational work isn&amp;rsquo;t visible (or worse, when I end up doing it on my own because it&amp;rsquo;s hard to justify in sprint planning) it creates the illusion that there&amp;rsquo;s only one stream of work: the product owner&amp;rsquo;s backlog. Feature requests, customer issues, integration tickets. That becomes the only work that feels real, the only work that gets discussed in standups, the only work that counts.&lt;/p&gt;
&lt;p&gt;Engineers naturally gravitate toward pleasing the PO. The PO has the backlog, the priorities, the stakeholder pressure. The CTO has&amp;hellip; a vague sense that the foundations need work. If I haven&amp;rsquo;t made the supply chain roadmap as concrete and visible as the feature backlog, I can&amp;rsquo;t blame anyone for treating it as optional.&lt;/p&gt;
&lt;p&gt;But it&amp;rsquo;s a dangerous dynamic. A team that only serves the PO&amp;rsquo;s backlog is a team that only adds features to an MVP while the foundations quietly rot. The PO&amp;rsquo;s job is to maximize customer value &lt;em&gt;now&lt;/em&gt;. The CTO&amp;rsquo;s job is to make sure the product can still deliver that value in two years. Those aren&amp;rsquo;t opposing goals, but they operate on different timescales, and if the long-term one isn&amp;rsquo;t articulated clearly enough, it loses every single sprint planning meeting.&lt;/p&gt;
&lt;p&gt;The vision exists. It&amp;rsquo;s in the progression from bought tiles to clean-room schema. It&amp;rsquo;s in Calculon sitting next to Valhalla. It&amp;rsquo;s in Elzar generating styles that match a tile schema we designed. The work speaks for itself, but only if someone tells the story clearly enough that it becomes a roadmap, not a side project. And that someone is me.&lt;/p&gt;
&lt;p&gt;There&amp;rsquo;s a harder question underneath all of this: is growing the technological asset even possible when the company is purely ARR-driven?&lt;/p&gt;
&lt;p&gt;ARR optimizes for now. Ship the feature that closes the deal. Fix the bug that stops the churn. Hit the number this quarter. Every sprint is a negotiation between what moves the metric today and what makes the product real. And the metric always wins, because it&amp;rsquo;s concrete: a number on a dashboard, a target in a board deck.&lt;/p&gt;
&lt;p&gt;The technological asset doesn&amp;rsquo;t have a dashboard. There&amp;rsquo;s no metric for &amp;ldquo;we now own our tile schema&amp;rdquo; or &amp;ldquo;we can debug a routing issue in hours instead of weeks.&amp;rdquo; The value is real but it compounds invisibly: faster iteration, fewer black-box surprises, the ability to say yes to product requests that would have been impossible before. None of that shows up in ARR until months or years later, and by then nobody connects the cause to the effect.&lt;/p&gt;
&lt;p&gt;A company that&amp;rsquo;s purely ARR-driven will always deprioritize foundational work. Not out of malice. Out of measurement. If the only thing that counts is what moves the number this quarter, then building the asset that makes next year&amp;rsquo;s numbers possible will always lose. And you end up in a trap: a product that looks successful from the revenue side while its foundations quietly thin out, making it harder and more expensive to deliver on the promises that drive that very revenue.&lt;/p&gt;
&lt;p&gt;The uncomfortable truth is that ARR and the technological asset need each other. ARR without the asset is a product that gets harder to maintain every quarter. The asset without ARR is an engineering project with no customers. The CTO&amp;rsquo;s job is to make the case that both need investment, and that starving one to feed the other is a debt that always comes due.&lt;/p&gt;
&lt;p&gt;You start by depending. You end by being accountable. What happens in between is the difference between an MVP and a product.&lt;/p&gt;</description></item><item><title>Multi-arch Docker builds in GitHub Actions</title><link>https://man-you.ringum.net/posts/multi-arch-docker/</link><pubDate>Thu, 19 Feb 2026 10:00:00 +0200</pubDate><guid>https://man-you.ringum.net/posts/multi-arch-docker/</guid><description>&lt;p&gt;We needed ARM64 containers. Our Python services run on mixed infrastructure: amd64 in CI and some production clusters, arm64 on newer nodes. Building on one arch and emulating the other with QEMU was painfully slow and broke native extensions. So we added proper multi-arch builds to our GitHub Actions CI.&lt;/p&gt;
&lt;p&gt;It took a week to get right. Then five more fixes over two weeks as each failure mode revealed itself in production. This is what went wrong and how we fixed it.&lt;/p&gt;
&lt;h2 id="the-setup"&gt;The setup&lt;/h2&gt;
&lt;p&gt;We use reusable workflows in a shared &lt;code&gt;Woosmap/.github&lt;/code&gt; repo. Individual service repos call these. A &lt;code&gt;build-helper&lt;/code&gt; repo (checked out at runtime) contains the actual logic as a single Node.js action that routes to TypeScript modules based on an &lt;code&gt;action&lt;/code&gt; input:&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;figcaption&gt;build-helper/src/main.ts&lt;/figcaption&gt;
&lt;div class="highlight" title="build-helper/src/main.ts"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;build&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;build&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;get_build_info&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt; &lt;span class="k"&gt;break&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;publish&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;publish&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;get_build_info&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt; &lt;span class="k"&gt;break&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;release&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;release&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;get_build_info&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt; &lt;span class="k"&gt;break&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;create_manifest&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;createManifest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;get_build_info&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt; &lt;span class="k"&gt;break&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;create_release_manifest&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;createReleaseManifests&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;get_build_info&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt; &lt;span class="k"&gt;break&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;The multi-arch matrix is generated dynamically in the workflow:&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;figcaption&gt;.github/workflows/sonar_python_bender_build.yml&lt;/figcaption&gt;
&lt;div class="highlight" title=".github/workflows/sonar_python_bender_build.yml"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;strategy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;matrix&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;include&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;${{ fromJson(inputs.multi_arch &amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;[{&amp;#34;runner&amp;#34;:&amp;#34;ubuntu-24.04&amp;#34;,&amp;#34;arch&amp;#34;:&amp;#34;amd64&amp;#34;},
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt; {&amp;#34;runner&amp;#34;:&amp;#34;ubuntu-24.04-arm&amp;#34;,&amp;#34;arch&amp;#34;:&amp;#34;arm64&amp;#34;}]&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;|| &amp;#39;[{&amp;#34;runner&amp;#34;:&amp;#34;ubuntu-24.04&amp;#34;,&amp;#34;arch&amp;#34;:&amp;#34;amd64&amp;#34;}]&amp;#39;) }}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;runs-on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;${{ matrix.runner }}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;When &lt;code&gt;multi_arch&lt;/code&gt; is true, two jobs run in parallel on native runners. No QEMU. An &lt;code&gt;ARCH_SUFFIX&lt;/code&gt; environment variable (&lt;code&gt;-amd64&lt;/code&gt; or &lt;code&gt;-arm64&lt;/code&gt;) drives platform selection downstream:&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;figcaption&gt;build-helper/src/build.ts&lt;/figcaption&gt;
&lt;div class="highlight" title="build-helper/src/build.ts"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;getPlatformForArch&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;archSuffix&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;getArchSuffix&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;archSuffix&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;-arm64&amp;#39;&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;linux/arm64&amp;#39;&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;linux/amd64&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;ARM64 jobs skip tests, coverage, and SonarCloud. Only amd64 does those. Build + push only for arm64.&lt;/p&gt;
&lt;h2 id="problem-1-buildx-attestations"&gt;Problem 1: buildx attestations&lt;/h2&gt;
&lt;p&gt;Buildx 0.10+ defaults to pushing attestation manifests (SBOM, provenance) alongside your image. When you &lt;code&gt;docker push myimage:tag&lt;/code&gt;, it doesn&amp;rsquo;t push a plain image, it pushes a &lt;strong&gt;manifest list&lt;/strong&gt; containing the image plus attestation layers.&lt;/p&gt;
&lt;p&gt;This is fine if you just pull and run. But we need to stitch arch-specific images into a multi-arch manifest:&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;docker manifest create myimage:pr_10 &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --amend myimage:sha-amd64 &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --amend myimage:sha-arm64&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;&lt;code&gt;docker manifest create&lt;/code&gt; expects plain images as sources, not manifest lists. When &lt;code&gt;sha-amd64&lt;/code&gt; is itself a manifest list (because of attestations), the stitching fails.&lt;/p&gt;
&lt;p&gt;The fix: disable attestations with &lt;code&gt;BUILDX_NO_DEFAULT_ATTESTATIONS=1&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Our first attempt was conditional:&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# Broken: empty string &amp;#34;&amp;#34; fails strconv.ParseBool&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;BUILDX_NO_DEFAULT_ATTESTATIONS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;${{ inputs.multi_arch &amp;amp;&amp;amp; &amp;#39;1&amp;#39; || &amp;#39;&amp;#39; }}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;When &lt;code&gt;multi_arch&lt;/code&gt; is false, this evaluates to &lt;code&gt;&amp;quot;&amp;quot;&lt;/code&gt;. Buildx tries to parse it as a boolean via Go&amp;rsquo;s &lt;code&gt;strconv.ParseBool&lt;/code&gt;, which fails on empty string. This broke &lt;strong&gt;every non-multi-arch build&lt;/strong&gt; across the organization.&lt;/p&gt;
&lt;p&gt;The fix was trivial, always set it to &lt;code&gt;1&lt;/code&gt;:&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;BUILDX_NO_DEFAULT_ATTESTATIONS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;Attestations aren&amp;rsquo;t needed regardless of arch mode. An env var being set to empty is different from not being set at all: a subtle but organization-wide footgun.&lt;/p&gt;
&lt;h2 id="problem-2-pr-number-resolution"&gt;Problem 2: PR number resolution&lt;/h2&gt;
&lt;p&gt;Each arch job publishes its image with a tag. We need a consistent tag so the manifest step can find both. The original design used &lt;code&gt;pr_X-amd64&lt;/code&gt; / &lt;code&gt;pr_X-arm64&lt;/code&gt;, where the PR number came from an API call:&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;figcaption&gt;build-helper/src/utils.ts (before)&lt;/figcaption&gt;
&lt;div class="highlight" title="build-helper/src/utils.ts (before)"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;get_pr&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;number&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;: &lt;span class="kt"&gt;commit_prs&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;oktokit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;repos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;listPullRequestsAssociatedWithCommit&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;owner&lt;/span&gt;: &lt;span class="kt"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nx"&gt;repo&lt;/span&gt;: &lt;span class="kt"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nx"&gt;commit_sha&lt;/span&gt;: &lt;span class="kt"&gt;sha&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;commit_prs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Commit has no PR&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;commit_prs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="kt"&gt;number&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;The &lt;code&gt;listPullRequestsAssociatedWithCommit&lt;/code&gt; API sometimes returned empty results: race conditions, merge commits, event timing. When it failed, &lt;code&gt;get_publish_tag()&lt;/code&gt; silently fell back to the raw SHA:&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;figcaption&gt;build-helper/src/utils.ts (silent fallback)&lt;/figcaption&gt;
&lt;div class="highlight" title="build-helper/src/utils.ts (silent fallback)"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;get_publish_tag&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sb"&gt;`pr_&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;get_pr&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;GITHUB_SHA&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&amp;#39;&lt;/span&gt; &lt;span class="c1"&gt;// No warning!
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;The amd64 job would tag as &lt;code&gt;pr_42-amd64&lt;/code&gt; (API succeeded), the arm64 job as &lt;code&gt;abc123def&lt;/code&gt; (API failed). The manifest step looked for &lt;code&gt;pr_42-arm64&lt;/code&gt;, which didn&amp;rsquo;t exist.&lt;/p&gt;
&lt;p&gt;Two fixes:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Read PR number from the event payload&lt;/strong&gt;, always available, zero API calls:&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;figcaption&gt;build-helper/src/utils.ts (after)&lt;/figcaption&gt;
&lt;div class="highlight" title="build-helper/src/utils.ts (after)"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;get_pr&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;number&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;con&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;con&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pull_request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;con&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pull_request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;number&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// Fallback for push events only
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;: &lt;span class="kt"&gt;commit_prs&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;oktokit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;repos&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;listPullRequestsAssociatedWithCommit&lt;/span&gt;&lt;span class="p"&gt;({...})&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// ...
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;&lt;strong&gt;Use commit SHA for arch-suffixed images&lt;/strong&gt;, the SHA is always the same across parallel jobs:&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;figcaption&gt;build-helper/src/publish.ts&lt;/figcaption&gt;
&lt;div class="highlight" title="build-helper/src/publish.ts"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;docker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;image&lt;/span&gt;: &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;: &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;archSuffix&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;getArchSuffix&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sha&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;GITHUB_SHA&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tag&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;archSuffix&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;sha&lt;/span&gt;&lt;span class="si"&gt;}${&lt;/span&gt;&lt;span class="nx"&gt;archSuffix&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;get_publish_tag&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// ...
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;The manifest step bridges them:&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;figcaption&gt;build-helper/src/manifest.ts&lt;/figcaption&gt;
&lt;div class="highlight" title="build-helper/src/manifest.ts"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;manifestTag&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;get_publish_tag&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;// &amp;#34;pr_X&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sha&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;GITHUB_SHA&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;archTags&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ARCHITECTURES&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;arch&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;sha&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;-&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;arch&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;createAndPushManifest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;manifestTag&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;archTags&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;So the flow is: &lt;code&gt;{sha}-amd64&lt;/code&gt; + &lt;code&gt;{sha}-arm64&lt;/code&gt; → manifest &lt;code&gt;pr_X&lt;/code&gt;. No API race, no silent fallback.&lt;/p&gt;
&lt;h2 id="problem-3-deploy-ordering"&gt;Problem 3: deploy ordering&lt;/h2&gt;
&lt;p&gt;The Leela admin service deploys on every PR build. It was wired inline in the build job:&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Deploy&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;if&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;matrix.arch == &amp;#39;amd64&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;./helper/.github/actions/deploy&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;Problem: in multi-arch mode, the deploy step ran before the manifest existed. It tried to pull &lt;code&gt;pr_X&lt;/code&gt;, which only gets created in a separate &lt;code&gt;create_manifest&lt;/code&gt; job that depends on both arch builds completing.&lt;/p&gt;
&lt;p&gt;The fix adds a separate downstream job for multi-arch deploys:&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;figcaption&gt;.github/workflows/sonar_python_bender_build.yml&lt;/figcaption&gt;
&lt;div class="highlight" title=".github/workflows/sonar_python_bender_build.yml"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# Inline deploy only for single-arch&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Deploy&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;if&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;matrix.arch == &amp;#39;amd64&amp;#39; &amp;amp;&amp;amp; !inputs.multi_arch&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;./helper/.github/actions/deploy&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# Multi-arch: deploy after manifest is ready&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;pr_deploy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;if&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;inputs.multi_arch&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;needs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;create_manifest&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;runs-on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;ubuntu-24.04&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Deploy&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;./helper/.github/actions/deploy&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;Same pattern for the release workflow: deploy only after &lt;code&gt;create_release_manifest&lt;/code&gt; completes.&lt;/p&gt;
&lt;h2 id="problem-4-release-version-calculation"&gt;Problem 4: release version calculation&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;createReleaseManifests&lt;/code&gt; function creates the multi-arch manifest for release tags. It originally called &lt;code&gt;get_split_version_tag()&lt;/code&gt; to determine the version:&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;figcaption&gt;build-helper/src/release.ts (before)&lt;/figcaption&gt;
&lt;div class="highlight" title="build-helper/src/release.ts (before)"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tag&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;get_split_version_tag&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;// Fetches latest release, bumps version
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;version&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;.&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;.&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;&lt;code&gt;get_split_version_tag()&lt;/code&gt; fetches the latest GitHub release and bumps the version based on PR labels. But &lt;code&gt;createReleaseManifests&lt;/code&gt; runs &lt;strong&gt;after&lt;/strong&gt; the release jobs complete. The release already happened. So &lt;code&gt;get_latest_tag()&lt;/code&gt; now returns &lt;code&gt;v5.5.3&lt;/code&gt; (the new release), and bumping it gives &lt;code&gt;v5.5.4&lt;/code&gt;, which doesn&amp;rsquo;t exist as a tag.&lt;/p&gt;
&lt;p&gt;The fix: just use &lt;code&gt;get_latest_tag()&lt;/code&gt; directly.&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;figcaption&gt;build-helper/src/release.ts (after)&lt;/figcaption&gt;
&lt;div class="highlight" title="build-helper/src/release.ts (after)"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;version&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;get_latest_tag&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;parts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;version&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;.&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;archTags&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ARCHITECTURES&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;arch&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;version&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;-&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;arch&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;createAndPushManifest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;version&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;archTags&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// v1.2.3
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;createAndPushManifest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;.&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;archTags&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// v1.2
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;createAndPushManifest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nx"&gt;archTags&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// v1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;Three manifest tags per release. Semver consumers can pin to major, minor, or patch.&lt;/p&gt;
&lt;h2 id="the-full-tagging-flow"&gt;The full tagging flow&lt;/h2&gt;
&lt;p&gt;After all the fixes, the image lifecycle looks like this:&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;PR Build:
{sha}-amd64, {sha}-arm64 → manifest: pr_X
Release (per-arch):
pull pr_X (Docker resolves correct arch)
push v1.2.3-amd64, v1.2.3-arm64
Release Manifests:
v1.2.3-amd64 + v1.2.3-arm64 → manifests: v1.2.3, v1.2, v1&lt;/code&gt;&lt;/pre&gt;
&lt;/figure&gt;
&lt;p&gt;Only the amd64 job creates the GitHub release to avoid duplicates:&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;figcaption&gt;build-helper/src/release.ts&lt;/figcaption&gt;
&lt;div class="highlight" title="build-helper/src/release.ts"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;archSuffix&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;archSuffix&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;-amd64&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;create_release&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;version&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;core&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sb"&gt;`Skipping GitHub release creation on &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;archSuffix&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt; build`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;h2 id="timeline"&gt;Timeline&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Feb 4&lt;/strong&gt;: Initial multi-arch support. Matrix strategy, arch suffix, manifest creation.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Feb 5&lt;/strong&gt;: Deploy ordering fix. Leela deploying before manifests existed.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Feb 16&lt;/strong&gt;: Same fix for the release/merge workflow.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Feb 17&lt;/strong&gt;: &lt;code&gt;BUILDX_NO_DEFAULT_ATTESTATIONS&lt;/code&gt; to fix attestation-poisoned manifests.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Feb 18&lt;/strong&gt;: SHA-based arch tagging + PR number from event payload.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Feb 19&lt;/strong&gt;: &lt;code&gt;BUILDX_NO_DEFAULT_ATTESTATIONS&lt;/code&gt; empty string crash fix.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Feb 19&lt;/strong&gt;: &lt;code&gt;get_latest_tag()&lt;/code&gt; instead of &lt;code&gt;get_split_version_tag()&lt;/code&gt; in release manifests.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Five fixes in two weeks after the initial rollout. Each one discovered in production by a different failure mode.&lt;/p&gt;
&lt;h2 id="what-id-do-differently"&gt;What I&amp;rsquo;d do differently&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Test the non-multi-arch path.&lt;/strong&gt; The empty string &lt;code&gt;strconv.ParseBool&lt;/code&gt; crash only affected repos that hadn&amp;rsquo;t opted into multi-arch. We tested the new feature, not the old path.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Use SHA tags from the start.&lt;/strong&gt; The PR-number-based approach was elegant but fragile. The SHA is always consistent across parallel jobs, no API calls, no race conditions.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Make the manifest step a required downstream job.&lt;/strong&gt; The deploy-before-manifest problem was obvious in retrospect. Any step that depends on a multi-arch manifest should be in a job that &lt;code&gt;needs:&lt;/code&gt; the manifest creation job. No exceptions, no &amp;ldquo;it works for single-arch&amp;rdquo; shortcuts.&lt;/p&gt;
&lt;p&gt;Docker&amp;rsquo;s multi-arch story is good once it works. Getting there from a single-arch CI that evolved organically over years is where the pain lives. Every assumption about &amp;ldquo;one build produces one image&amp;rdquo; breaks in interesting ways.&lt;/p&gt;</description></item><item><title>Full-text search and lyrics in a Go music server</title><link>https://man-you.ringum.net/posts/tunes-search-lyrics/</link><pubDate>Wed, 18 Feb 2026 18:00:00 +0200</pubDate><guid>https://man-you.ringum.net/posts/tunes-search-lyrics/</guid><description>&lt;p&gt;The &lt;a href="https://man-you.ringum.net/posts/tunes/"&gt;Tunes&lt;/a&gt; server started as a way to stream my music library from a Raspberry Pi. It&amp;rsquo;s accumulated features since then. Two recent additions changed how I interact with it day-to-day: accent-insensitive full-text search across the entire library, and lyrics, both embedded from audio files and fetched from LRClib.&lt;/p&gt;
&lt;h2 id="the-search-problem"&gt;The search problem&lt;/h2&gt;
&lt;p&gt;Searching a music library is harder than it sounds. You want &amp;ldquo;bjork&amp;rdquo; to match &amp;ldquo;Björk&amp;rdquo;. You want &amp;ldquo;cafe&amp;rdquo; to match &amp;ldquo;Café&amp;rdquo;. You want &amp;ldquo;creme&amp;rdquo; to match &amp;ldquo;Crème Brûlée&amp;rdquo; in an album title. And you want it fast enough that results appear as you type.&lt;/p&gt;
&lt;p&gt;The previous search was a LIKE query with wildcards. It worked, but it couldn&amp;rsquo;t handle diacritics and got slow on a 15,000-track library with multi-column matches.&lt;/p&gt;
&lt;h2 id="fts5-with-unicode61"&gt;FTS5 with unicode61&lt;/h2&gt;
&lt;p&gt;SQLite&amp;rsquo;s FTS5 extension does exactly what we need. The &lt;code&gt;unicode61&lt;/code&gt; tokenizer with &lt;code&gt;remove_diacritics 2&lt;/code&gt; strips all Unicode diacritical marks during indexing and querying:&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;figcaption&gt;tunes/search_fts.go: FTS5 table creation&lt;/figcaption&gt;
&lt;div class="highlight" title="tunes/search_fts.go: FTS5 table creation"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;SearchFTS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;InitializeFTS&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;createSQL&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;`
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; CREATE VIRTUAL TABLE IF NOT EXISTS tracks_fts USING fts5(
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; track_id UNINDEXED,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; name,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; artist,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; album_artist,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; album,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; composer,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; genre,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; tokenize=&amp;#39;unicode61 remove_diacritics 2&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; )`&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;createSQL&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;strings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;no such module: fts5&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Sugar&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Warn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;FTS5 module not available - search will be disabled.&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fts5Available&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;failed to create FTS5 table: %w&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fts5Available&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;&lt;code&gt;track_id UNINDEXED&lt;/code&gt; stores the ID in the FTS table for joins but doesn&amp;rsquo;t tokenize it. If FTS5 isn&amp;rsquo;t compiled into the SQLite build, search degrades gracefully instead of crashing.&lt;/p&gt;
&lt;p&gt;The index rebuilds atomically in a transaction: delete all FTS data, repopulate from the tracks table using &lt;code&gt;COALESCE&lt;/code&gt; for NULL safety:&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;figcaption&gt;tunes/search_fts.go: index rebuild&lt;/figcaption&gt;
&lt;div class="highlight" title="tunes/search_fts.go: index rebuild"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;SearchFTS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;RebuildIndex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;Transaction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;gorm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DB&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;DELETE FROM tracks_fts&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;failed to clear FTS table: %w&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;populateSQL&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;`
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; INSERT INTO tracks_fts (track_id, name, artist, album_artist,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; album, composer, genre)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; SELECT track_id, COALESCE(name, &amp;#39;&amp;#39;), COALESCE(artist, &amp;#39;&amp;#39;),
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; COALESCE(album_artist, &amp;#39;&amp;#39;), COALESCE(album, &amp;#39;&amp;#39;),
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; COALESCE(composer, &amp;#39;&amp;#39;), COALESCE(genre, &amp;#39;&amp;#39;)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; FROM i_tunes_tracks WHERE deleted_at IS NULL`&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;populateSQL&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;Error&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;h2 id="query-building"&gt;Query building&lt;/h2&gt;
&lt;p&gt;User queries are split into words, each gets a &lt;code&gt;*&lt;/code&gt; suffix for prefix matching, and they&amp;rsquo;re joined with implicit AND. FTS5 special characters are stripped to prevent injection:&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;figcaption&gt;tunes/search_fts.go: query sanitization&lt;/figcaption&gt;
&lt;div class="highlight" title="tunes/search_fts.go: query sanitization"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;buildFTSQuery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;words&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;strings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Fields&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;strings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;TrimSpace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;parts&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;word&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;range&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;words&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;escaped&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;escapeFTSToken&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;word&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;escaped&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;&amp;#34;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;parts&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;escaped&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;*&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;strings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34; &amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;escapeFTSToken&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;strings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Builder&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;range&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;switch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;case&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sc"&gt;&amp;#39;&amp;#34;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sc"&gt;&amp;#39;*&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sc"&gt;&amp;#39;^&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sc"&gt;&amp;#39;(&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sc"&gt;&amp;#39;)&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sc"&gt;&amp;#39;{&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sc"&gt;&amp;#39;}&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sc"&gt;&amp;#39;[&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sc"&gt;&amp;#39;]&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sc"&gt;&amp;#39;:&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sc"&gt;&amp;#39;-&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteRune&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;String&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;Typing &amp;ldquo;aphex win&amp;rdquo; becomes &lt;code&gt;aphex* win*&lt;/code&gt;, which matches &amp;ldquo;Aphex Twin&amp;rdquo; via prefix expansion. Typing &amp;ldquo;cafe del&amp;rdquo; matches &amp;ldquo;Café Del Mar&amp;rdquo; because the tokenizer already stripped the accent at index time.&lt;/p&gt;
&lt;h2 id="unified-search-endpoint"&gt;Unified search endpoint&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;/search?q=&lt;/code&gt; endpoint returns artists, albums, and tracks in a single response, each ranked independently by BM25:&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;figcaption&gt;tunes/search_handler.go&lt;/figcaption&gt;
&lt;div class="highlight" title="tunes/search_handler.go"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;UnifiedSearchResponse&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Artists&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="nx"&gt;ArtistInfo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;`json:&amp;#34;artists&amp;#34;`&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Albums&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="nx"&gt;AlbumInfo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;`json:&amp;#34;albums&amp;#34;`&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Tracks&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="nx"&gt;ITunesTrack&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;`json:&amp;#34;tracks&amp;#34;`&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ls&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;LibraryService&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;HandleSearch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;w&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ResponseWriter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;strings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;TrimSpace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Query&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;q&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;artists&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ls&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;searchArtistsFTS&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;albums&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ls&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;searchAlbumsFTS&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;tracks&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ls&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;searchTracksFTS&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// ...&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;Each section queries specific FTS5 columns. Artist search hits both &lt;code&gt;artist&lt;/code&gt; and &lt;code&gt;album_artist&lt;/code&gt;, then merges and aggregates. The BM25 scoring comes from FTS5&amp;rsquo;s built-in function:&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;track_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;bm25&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tracks_fts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rank&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;tracks_fts&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;tracks_fts&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;MATCH&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;ORDER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;BY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rank&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;LIMIT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;BM25 order is preserved through joins by building a track map and reordering after the GORM query:&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;figcaption&gt;tunes/search_handler.go: preserving BM25 order&lt;/figcaption&gt;
&lt;div class="highlight" title="tunes/search_handler.go: preserving BM25 order"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;trackMap&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="nx"&gt;ITunesTrack&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tracks&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;range&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;tracks&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;TrackID&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;trackMap&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;TrackID&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;ordered&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="nx"&gt;ITunesTrack&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;trackIDs&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;range&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;trackIDs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;trackMap&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ordered&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ordered&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;Album search uses a two-stage approach: FTS returns track IDs, those map to &lt;code&gt;album_key&lt;/code&gt;, then the &lt;code&gt;album_cache&lt;/code&gt; table provides pre-aggregated album metadata. This avoids re-aggregating album info on every search.&lt;/p&gt;
&lt;h2 id="lyrics-from-audio-files"&gt;Lyrics from audio files&lt;/h2&gt;
&lt;p&gt;The music scanner already walks the filesystem to index tracks using &lt;code&gt;github.com/dhowden/tag&lt;/code&gt;. Adding lyrics extraction was a matter of probing the right metadata keys:&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;figcaption&gt;tunes/music_scanner.go: lyrics extraction&lt;/figcaption&gt;
&lt;div class="highlight" title="tunes/music_scanner.go: lyrics extraction"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ms&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;MusicScanner&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;extractLyricsFromMetadata&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rawMetadata&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;ExtractedLyrics&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;lyrics&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ExtractedLyrics&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;switch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;raw&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;rawMetadata&lt;/span&gt;&lt;span class="p"&gt;.(&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;case&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="kt"&gt;any&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;range&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;lyrics&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;USLT&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;unsynchronised_lyrics&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;unsynced_lyrics&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;LYRICS&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;\xa9lyr&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;val&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;switch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;val&lt;/span&gt;&lt;span class="p"&gt;.(&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;case&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;&amp;#34;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;lyrics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PlainLyrics&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;case&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;lyrics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PlainLyrics&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// Also check SYLT for synchronized lyrics&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;range&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;SYLT&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;synchronised_lyrics&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;synced_lyrics&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// ... same pattern&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;lyrics&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;&lt;code&gt;USLT&lt;/code&gt; is the ID3v2 unsynchronized lyrics frame, &lt;code&gt;&amp;quot;\xa9lyr&amp;quot;&lt;/code&gt; is the M4A/AAC lyrics atom, &lt;code&gt;SYLT&lt;/code&gt; is synchronized (timestamped) lyrics. The scanner handles both plain text and timestamped LRC format.&lt;/p&gt;
&lt;h2 id="lrclib-integration"&gt;LRClib integration&lt;/h2&gt;
&lt;p&gt;For tracks without embedded lyrics, the server fetches from &lt;a href="https://lrclib.net"&gt;LRClib&lt;/a&gt;, a free, open lyrics database with synced timestamps.&lt;/p&gt;
&lt;p&gt;The lyrics service uses &lt;code&gt;golang.org/x/time/rate&lt;/code&gt; to respect API limits:&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;figcaption&gt;tunes/lyrics_service.go&lt;/figcaption&gt;
&lt;div class="highlight" title="tunes/lyrics_service.go"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;lrcLibBaseURL&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;https://lrclib.net/api&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;lrcLibUserAgent&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Tunes/1.0 (https://github.com/manz/tunes-server)&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;lrcLibRateLimit&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;NewLyricsService&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;gorm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DB&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;LyricsService&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;LyricsService&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;NewLyricsRepository&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;httpClient&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;Timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Second&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;rateLimiter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;rate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;NewLimiter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Limit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;lrcLibRateLimit&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;When fetching, the service validates that the returned lyrics match the track&amp;rsquo;s duration within 5 seconds. This prevents mismatches: same song title by different artists, live vs studio versions:&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;figcaption&gt;tunes/lyrics_service.go: duration tolerance&lt;/figcaption&gt;
&lt;div class="highlight" title="tunes/lyrics_service.go: duration tolerance"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;duration&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;lrcResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Duration&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;float64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;lrcResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;durationTolerance&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ErrLyricsNotFound&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;The handler implements fetch-through caching: check the database first, if miss then fetch from LRClib, save, and return. No external call on subsequent requests for the same track.&lt;/p&gt;
&lt;p&gt;Lyrics are stored with GORM&amp;rsquo;s &lt;code&gt;FirstOrCreate&lt;/code&gt; + &lt;code&gt;Assign&lt;/code&gt; for clean upserts:&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;figcaption&gt;tunes/lyrics_repository.go&lt;/figcaption&gt;
&lt;div class="highlight" title="tunes/lyrics_repository.go"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;LyricsRepository&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;SaveLyrics&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;lyrics&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;TrackLyrics&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;Where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;TrackLyrics&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;TrackID&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;lyrics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;TrackID&lt;/span&gt;&lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;Assign&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;TrackLyrics&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;LRCLibID&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;lyrics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;LRCLibID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Instrumental&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;lyrics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Instrumental&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;PlainLyrics&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;lyrics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PlainLyrics&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;SyncedLyrics&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;lyrics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SyncedLyrics&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Source&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;lyrics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Source&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;FirstOrCreate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;lyrics&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;Error&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;h2 id="background-enrichment"&gt;Background enrichment&lt;/h2&gt;
&lt;p&gt;A &lt;code&gt;POST /lyrics/enrich&lt;/code&gt; endpoint triggers a goroutine that processes tracks without lyrics in batches of 50. It finds un-enriched tracks with a LEFT JOIN:&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;figcaption&gt;tunes/lyrics_repository.go: finding tracks without lyrics&lt;/figcaption&gt;
&lt;div class="highlight" title="tunes/lyrics_repository.go: finding tracks without lyrics"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Joins&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;LEFT JOIN track_lyrics ON i_tunes_tracks.track_id = track_lyrics.track_id&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;Where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;track_lyrics.id IS NULL&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;Where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;i_tunes_tracks.deleted_at IS NULL&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;Limit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;Find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;tracks&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;Error&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;Rate-limited at 2 requests per second, a 15,000-track library takes a few hours to fully enrich. The mutex prevents concurrent enrichment runs:&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;figcaption&gt;tunes/lyrics_service.go: single-flight enrichment&lt;/figcaption&gt;
&lt;div class="highlight" title="tunes/lyrics_service.go: single-flight enrichment"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;LyricsService&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;StartBackgroundEnrichment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Lock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isEnriching&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Unlock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isEnriching&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Unlock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;go&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;defer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Lock&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isEnriching&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Unlock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;tracks&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetTracksWithoutLyrics&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// ... process batch, respect rate limiter&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;h2 id="admin-via-sudoers"&gt;Admin via sudoers&lt;/h2&gt;
&lt;p&gt;The authorization model is Unix-inspired. Instead of a boolean &lt;code&gt;is_admin&lt;/code&gt; on the User model, there&amp;rsquo;s a dedicated &lt;code&gt;sudoers&lt;/code&gt; table with audit trail:&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;figcaption&gt;tunes/models.go&lt;/figcaption&gt;
&lt;div class="highlight" title="tunes/models.go"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Sudoer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ID&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;`gorm:&amp;#34;primaryKey;autoIncrement&amp;#34;`&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;UserID&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;`gorm:&amp;#34;column:user_id;uniqueIndex;not null&amp;#34;`&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;GrantedBy&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;`gorm:&amp;#34;column:granted_by;not null&amp;#34;`&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;GrantedAt&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Time&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;`gorm:&amp;#34;autoCreateTime&amp;#34;`&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ExpiresAt&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Time&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;`gorm:&amp;#34;column:expires_at&amp;#34;`&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Active&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;`gorm:&amp;#34;column:active;default:true&amp;#34;`&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;Destructive operations (deleting tracks, managing users) additionally require a sudo token, obtained by re-authenticating via WebAuthn passkey. Two-step elevation: request a challenge, prove you&amp;rsquo;re you, get a time-limited token. This prevents a stolen session from doing real damage.&lt;/p&gt;
&lt;p&gt;Routes use higher-order functions for auth gating:&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;figcaption&gt;cmd/tunes-server/main.go&lt;/figcaption&gt;
&lt;div class="highlight" title="cmd/tunes-server/main.go"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;sudoerRoute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;authMiddleware&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Handler&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Handler&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;authService&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;AuthService&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;HandlerFunc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Handler&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;handler&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;HandlerFunc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Handler&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;authMiddleware&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;requireSudoerMiddleware&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;authService&lt;/span&gt;&lt;span class="p"&gt;)(&lt;/span&gt;&lt;span class="nx"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;The first user must be created via CLI before the server starts: &lt;code&gt;tunes-server users add &amp;lt;username&amp;gt; --sudoer&lt;/code&gt;. No web-based registration for the initial admin. You need shell access.&lt;/p&gt;
&lt;h2 id="what-changed"&gt;What changed&lt;/h2&gt;
&lt;p&gt;The server went from &amp;ldquo;stream my library&amp;rdquo; to &amp;ldquo;actually find what I want to play.&amp;rdquo; FTS5 with accent stripping makes search work the way you&amp;rsquo;d expect: type a rough approximation of the artist name, get the right results. Lyrics with synced timestamps let the iOS app show karaoke-style scrolling text. Background enrichment means I didn&amp;rsquo;t have to manually tag anything. It just filled in over a couple of evenings.&lt;/p&gt;
&lt;p&gt;Still no replacement for Apple Music&amp;rsquo;s catalog. But for my 15,000 tracks, it works better than iTunes Match ever did.&lt;/p&gt;</description></item><item><title>Calculon part 2: costing profiles, performance, and maneuvers</title><link>https://man-you.ringum.net/posts/calculon-part2/</link><pubDate>Wed, 18 Feb 2026 10:00:00 +0200</pubDate><guid>https://man-you.ringum.net/posts/calculon-part2/</guid><description>&lt;p&gt;&lt;figure&gt;
&lt;img src="https://man-you.ringum.net/posts/calculon-part2/calculon.svg" alt="Calculon" /&gt;
&lt;figcaption&gt;Why can&amp;#39;t I move on?&lt;/figcaption&gt;
&lt;/figure&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://man-you.ringum.net/posts/calculon/"&gt;Part 1&lt;/a&gt; covered the basics: six crates, Valhalla&amp;rsquo;s tile format, a driving cost model, bidirectional A*, many-to-many matrix, and an Axum HTTP server. Everything worked for the driving case on the Monaco dataset.&lt;/p&gt;
&lt;p&gt;Since then, the engine gained bicycle and pedestrian costing, memory-mapped tiles, a comparison webapp, turn-by-turn directions in 34 languages, and enough performance work to make it usable on France-scale data. This is what changed.&lt;/p&gt;
&lt;h2 id="bicycle-and-pedestrian-costing"&gt;Bicycle and pedestrian costing&lt;/h2&gt;
&lt;p&gt;Two new cost models implement the &lt;code&gt;DynamicCost&lt;/code&gt; trait alongside the existing &lt;code&gt;AutoCost&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Bicycle&lt;/strong&gt; defaults to 18 kph (hybrid bike). The model factors in grade, road stress, surface quality, and turn costs, all lighter than auto because a cyclist doesn&amp;rsquo;t mind a left turn as much as a driver crossing oncoming traffic.&lt;/p&gt;
&lt;p&gt;Grade uses a Tobler-style speed table. Flat terrain is 1.0x; steep downhill goes up to 2.2x; steep uphill drops to 0.3x. Hill avoidance is a separate preference (&lt;code&gt;use_hills&lt;/code&gt;) that applies an additional penalty on grades regardless of the speed adjustment.&lt;/p&gt;
&lt;p&gt;The interesting part is road stress, how comfortable a road is to cycle on:&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;figcaption&gt;crates/calculon-costing/src/bicycle_cost.rs&lt;/figcaption&gt;
&lt;div class="highlight" title="crates/calculon-costing/src/bicycle_cost.rs"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-rust" data-lang="rust"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;accommodation_factor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;edge&lt;/span&gt;: &lt;span class="kp"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nc"&gt;DirectedEdge&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-&amp;gt; &lt;span class="kt"&gt;f32&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;use_type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;edge&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;use_type&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;use_type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Use&lt;/span&gt;::&lt;span class="n"&gt;Cycleway&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;u32&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;||&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;use_type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Use&lt;/span&gt;::&lt;span class="n"&gt;MountainBike&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;u32&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;match&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;edge&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cycle_lane&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;CycleLane&lt;/span&gt;::&lt;span class="n"&gt;Separated&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;CycleLane&lt;/span&gt;::&lt;span class="n"&gt;Dedicated&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;CycleLane&lt;/span&gt;::&lt;span class="n"&gt;Shared&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;CycleLane&lt;/span&gt;::&lt;span class="nb"&gt;None&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;edge&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bike_network&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;edge&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shoulder&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;A separated cycle lane costs nothing extra. A shared lane adds 0.3. No accommodation at all on a road that&amp;rsquo;s not part of any bike network costs 1.0, scaled by the &lt;code&gt;use_roads&lt;/code&gt; preference. Combined with &lt;code&gt;ROAD_CLASS_FACTOR&lt;/code&gt; that penalizes trunk roads (0.8) vs residential (0.0), the engine steers cyclists toward quieter streets with bike infrastructure.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Pedestrian&lt;/strong&gt; defaults to 5.1 kph. It adds SAC hiking scale (7-tier trail difficulty), a step penalty for stairs, lighting preference, and use-type bonuses for footways and sidewalks:&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;figcaption&gt;crates/calculon-costing/src/pedestrian_cost.rs&lt;/figcaption&gt;
&lt;div class="highlight" title="crates/calculon-costing/src/pedestrian_cost.rs"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-rust" data-lang="rust"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;GRADE_SPEED_FACTOR&lt;/span&gt;: &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;f32&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1.4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1.3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1.15&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1.05&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// downhill: slightly faster
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// flat
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.95&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.85&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.75&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.65&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.55&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.45&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.35&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// uphill: slower
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;];&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;Both profiles make a critical architectural decision: they &lt;strong&gt;disable hierarchy entirely&lt;/strong&gt;. The A* hierarchy transitions work by &amp;ldquo;jumping up&amp;rdquo; to highways as the search moves away from endpoints. That makes sense for driving: a 200km route shouldn&amp;rsquo;t expand residential streets in the middle. But for cycling and walking, the shortcut edges that power hierarchy are built for driving and would produce wrong routes. So bicycle and pedestrian stay on level 2 where all edges are accessible, trading search speed for correctness.&lt;/p&gt;
&lt;h2 id="memory-mapped-tiles"&gt;Memory-mapped tiles&lt;/h2&gt;
&lt;p&gt;The tile reader now uses &lt;code&gt;mmap(2)&lt;/code&gt; instead of heap-allocated reads:&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;figcaption&gt;crates/calculon-tiles/src/graph_tile.rs&lt;/figcaption&gt;
&lt;div class="highlight" title="crates/calculon-tiles/src/graph_tile.rs"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-rust" data-lang="rust"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;enum&lt;/span&gt; &lt;span class="nc"&gt;TileData&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Owned&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;u8&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// Tests
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Mapped&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Mmap&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// Production
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;pub&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;from_mmap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mmap&lt;/span&gt;: &lt;span class="nc"&gt;Mmap&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-&amp;gt; &lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="bp"&gt;Self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;TileError&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;base_ll&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;Self&lt;/span&gt;::&lt;span class="n"&gt;validate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;mmap&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;GraphTile&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;: &lt;span class="nc"&gt;TileData&lt;/span&gt;::&lt;span class="n"&gt;Mapped&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mmap&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;base_ll&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;In the graph reader:&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;figcaption&gt;crates/calculon-tiles/src/graph_reader.rs&lt;/figcaption&gt;
&lt;div class="highlight" title="crates/calculon-tiles/src/graph_reader.rs"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-rust" data-lang="rust"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;::&lt;span class="n"&gt;fs&lt;/span&gt;::&lt;span class="n"&gt;File&lt;/span&gt;::&lt;span class="n"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;map_err&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TileError&lt;/span&gt;::&lt;span class="n"&gt;Io&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;mmap&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;unsafe&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Mmap&lt;/span&gt;::&lt;span class="n"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}.&lt;/span&gt;&lt;span class="n"&gt;map_err&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TileError&lt;/span&gt;::&lt;span class="n"&gt;Io&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;tile&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Arc&lt;/span&gt;::&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;GraphTile&lt;/span&gt;::&lt;span class="n"&gt;from_mmap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mmap&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;No heap allocation, no copy. The OS page cache handles eviction. The old application-level LRU cache became unnecessary. Tiles are cheap &lt;code&gt;Arc&lt;/code&gt; pointers to mapped pages.&lt;/p&gt;
&lt;h2 id="thread-local-tile-cache"&gt;Thread-local tile cache&lt;/h2&gt;
&lt;p&gt;Routing makes thousands of &lt;code&gt;get_tile&lt;/code&gt; calls per search. Even a &lt;code&gt;RwLock&lt;/code&gt; on a &lt;code&gt;HashMap&lt;/code&gt; adds up. The solution is a three-level cache:&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;figcaption&gt;crates/calculon-tiles/src/graph_reader.rs&lt;/figcaption&gt;
&lt;div class="highlight" title="crates/calculon-tiles/src/graph_reader.rs"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-rust" data-lang="rust"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="fm"&gt;thread_local!&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;TL_CACHE&lt;/span&gt;: &lt;span class="nc"&gt;RefCell&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;FxHashMap&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;u32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Arc&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;GraphTile&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;RefCell&lt;/span&gt;::&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;FxHashMap&lt;/span&gt;::&lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;pub&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;get_tile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;: &lt;span class="kp"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nc"&gt;GraphId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-&amp;gt; &lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Arc&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;GraphTile&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;TileError&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;tile_key&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tile_value&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// Level 1: thread-local (no synchronization, ~20ns)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;hit&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;TL_CACHE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;borrow&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;tile_key&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;cloned&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tile&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;hit&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tile&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// Level 2: shared cache (RwLock, parallel readers)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// ... insert into thread-local on hit
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// Level 3: mmap from disk, populate both caches
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// ...
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;The hot path (thread-local hit) is a plain &lt;code&gt;HashMap&lt;/code&gt; lookup in a &lt;code&gt;RefCell&lt;/code&gt;. No lock, no contention, no &lt;code&gt;Arc::clone&lt;/code&gt; from the shared cache. FxHash (from &lt;code&gt;rustc_hash&lt;/code&gt;) replaces &lt;code&gt;std::HashMap&lt;/code&gt; everywhere, optimized for integer keys that don&amp;rsquo;t need cryptographic hashing.&lt;/p&gt;
&lt;h2 id="distance-approximator"&gt;Distance approximator&lt;/h2&gt;
&lt;p&gt;Edge snapping calls distance computations in a tight loop, projecting a point onto every nearby road segment. Haversine uses &lt;code&gt;sin&lt;/code&gt;, &lt;code&gt;cos&lt;/code&gt;, &lt;code&gt;asin&lt;/code&gt;, &lt;code&gt;sqrt&lt;/code&gt; per call. For relative comparisons within a small area, that&amp;rsquo;s overkill.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;DistanceApproximator&lt;/code&gt; pre-computes &lt;code&gt;cos(lat)&lt;/code&gt; once and then uses only multiplications:&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;figcaption&gt;crates/calculon-core/src/geo.rs&lt;/figcaption&gt;
&lt;div class="highlight" title="crates/calculon-core/src/geo.rs"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-rust" data-lang="rust"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;pub&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="nc"&gt;DistanceApproximator&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;lng_scale&lt;/span&gt;: &lt;span class="kt"&gt;f64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;meters_per_lng_degree&lt;/span&gt;: &lt;span class="kt"&gt;f64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;impl&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;DistanceApproximator&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;pub&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ll&lt;/span&gt;: &lt;span class="kp"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nc"&gt;PointLL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-&amp;gt; &lt;span class="nc"&gt;Self&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;lng_scale&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;Self&lt;/span&gt;::&lt;span class="n"&gt;lng_scale_per_lat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ll&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lat&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;Self&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;lng_scale&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;meters_per_lng_degree&lt;/span&gt;: &lt;span class="nc"&gt;lng_scale&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;METERS_PER_DEGREE_LAT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;pub&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;distance_squared&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ll&lt;/span&gt;: &lt;span class="kp"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nc"&gt;PointLL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-&amp;gt; &lt;span class="kt"&gt;f64&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;lat_m&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ll&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lat&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;center_lat&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;METERS_PER_DEGREE_LAT&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;lng_m&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ll&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lng&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;center_lng&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;meters_per_lng_degree&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;lat_m&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;lat_m&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;lng_m&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;lng_m&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;Construct one for the search point, reuse it across all segment projections. 26% faster in edge snapping benchmarks. The accuracy loss is negligible within a few kilometers, well within the range of any snap search.&lt;/p&gt;
&lt;h2 id="distance-adaptive-hierarchy"&gt;Distance-adaptive hierarchy&lt;/h2&gt;
&lt;p&gt;Short urban routes were sometimes jumping to highways unnecessarily. A 2km trip through a city shouldn&amp;rsquo;t consider the nearby motorway just because the hierarchy said &amp;ldquo;expand to level 0 after 5,000 meters.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;The fix scales hierarchy limits proportionally to route distance:&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;figcaption&gt;crates/calculon-route/src/bidirectional_astar.rs&lt;/figcaption&gt;
&lt;div class="highlight" title="crates/calculon-route/src/bidirectional_astar.rs"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-rust" data-lang="rust"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;adjust_limits_for_distance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;limits&lt;/span&gt;: &lt;span class="kp"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nc"&gt;mut&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;HierarchyLimits&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;distance&lt;/span&gt;: &lt;span class="kt"&gt;f32&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// 5km route: expand_within_dist = 2500m, max_up_transitions = 11
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// 1km route: expand_within_dist = 2000m, max_up_transitions = 5
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// 20km+ route: no adjustment
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;A 5km route caps level-2 expansion at 2,500m and allows only 11 upward transitions (from 100 default). A 1km route drops to 5 transitions with a 2,000m cap. Routes above 15km keep the defaults. The search still finds the optimal path, it just doesn&amp;rsquo;t waste time on irrelevant highway segments for short trips.&lt;/p&gt;
&lt;h2 id="a-heuristics-in-the-cost-matrix"&gt;A* heuristics in the cost matrix&lt;/h2&gt;
&lt;p&gt;The matrix previously used pure Dijkstra for each source/target pair: expand uniformly in all directions. Now each gets an A* heuristic aimed at its closest counterpart:&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;figcaption&gt;crates/calculon-matrix/src/cost_matrix.rs&lt;/figcaption&gt;
&lt;div class="highlight" title="crates/calculon-matrix/src/cost_matrix.rs"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-rust" data-lang="rust"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;closest&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;targets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;iter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;min_by&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;da&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;distance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;distance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;da&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;partial_cmp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;unwrap_or&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;::&lt;span class="n"&gt;cmp&lt;/span&gt;::&lt;span class="n"&gt;Ordering&lt;/span&gt;::&lt;span class="n"&gt;Equal&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;unwrap_or&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;targets&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;closest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cost_factor&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;heuristics_forward&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;This steers each search toward likely connection points rather than expanding uniformly. Combined with Valhalla-style hierarchy and a &lt;code&gt;path_dist_threshold&lt;/code&gt; set to 2x the max source-target distance, the matrix handles 9x9 urban grids without exploring the entire region.&lt;/p&gt;
&lt;h2 id="turn-by-turn-maneuvers"&gt;Turn-by-turn maneuvers&lt;/h2&gt;
&lt;p&gt;The first version returned routes with &amp;ldquo;Start&amp;rdquo; and &amp;ldquo;Arrive&amp;rdquo; stubs. Now there&amp;rsquo;s a proper maneuver builder that groups consecutive edges into instructions.&lt;/p&gt;
&lt;p&gt;The builder triggers a new maneuver on name changes, significant turns, road type transitions (roundabouts, ramps, ferries):&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;figcaption&gt;crates/calculon/src/maneuver_builder.rs&lt;/figcaption&gt;
&lt;div class="highlight" title="crates/calculon/src/maneuver_builder.rs"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-rust" data-lang="rust"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;should_start_new_maneuver&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;in_roundabout&lt;/span&gt;: &lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-&amp;gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;entering_roundabout&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;||&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exiting_roundabout&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;||&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;entering_ramp&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;||&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exiting_ramp&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;||&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;entering_ferry&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;||&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exiting_ferry&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;||&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;turn_type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;TurnType&lt;/span&gt;::&lt;span class="n"&gt;Reverse&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;||&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name_changed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;in_roundabout&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;||&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;turn_type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;TurnType&lt;/span&gt;::&lt;span class="n"&gt;Straight&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name_changed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;Narrative generation uses &lt;code&gt;rust-i18n&lt;/code&gt; with 34 locale files. Each maneuver type maps to a template:&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;figcaption&gt;locales/en-US.yml&lt;/figcaption&gt;
&lt;div class="highlight" title="locales/en-US.yml"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;start.5&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Drive %{cardinal_direction} on %{street_names}.&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;turn.1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Turn %{relative_direction} onto %{street_names}.&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;enter_roundabout.2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Enter the roundabout and take the %{ordinal_value} exit onto %{roundabout_exit_street_names}.&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;destination.0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;You have arrived at your destination.&amp;#34;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;Roundabouts track exit counts. Cardinal directions derive from heading. Ordinals support up to the 10th exit. The locale files include French, German, Spanish, Italian, and (yes) pirate English.&lt;/p&gt;
&lt;h2 id="edge-snapping-improvements"&gt;Edge snapping improvements&lt;/h2&gt;
&lt;p&gt;Snapping a coordinate to the road network was one of the weaker points. Two fixes:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Multi-tile search.&lt;/strong&gt; When a point falls near a tile boundary, the search now checks neighboring tiles in all 8 directions:&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;figcaption&gt;crates/calculon-route/src/bidirectional_astar.rs&lt;/figcaption&gt;
&lt;div class="highlight" title="crates/calculon-route/src/bidirectional_astar.rs"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-rust" data-lang="rust"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;near_south&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;point&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lat&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;bbox&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;min_pt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lat&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;TILE_BOUNDARY_MARGIN&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;near_north&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;bbox&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;max_pt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lat&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;point&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lat&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;TILE_BOUNDARY_MARGIN&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// ... check all 8 directions
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dlng&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;dlat&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;offsets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;iter&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;flags&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;neighbor_id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;TileHierarchy&lt;/span&gt;::&lt;span class="n"&gt;get_graph_id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;neighbor_pt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;level&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tile&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_tile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;neighbor_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;candidates&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;tile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;find_closest_edges&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;point&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;access_mask&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="p"&gt;.);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;all&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;extend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;candidates&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;&lt;strong&gt;Smarter candidate pruning.&lt;/strong&gt; Post-collection, candidates beyond &lt;code&gt;min_distance * 3.0&lt;/code&gt; (minimum 25m threshold) are pruned, then &lt;code&gt;select_nth_unstable_by&lt;/code&gt; does an O(N) partition instead of a full sort. Only the top K candidates get sorted.&lt;/p&gt;
&lt;h2 id="all-my-circuits"&gt;All My Circuits&lt;/h2&gt;
&lt;p&gt;A Lit + TypeScript + MapLibre GL webapp for side-by-side comparison with Valhalla. Route and matrix modes, all three costing profiles, Valhalla comparison with percentage diffs on time and distance, resizable sidebar, URL state persistence, clickable matrix routes in verbose mode, and links to Google Maps and HERE for sanity checking.&lt;/p&gt;
&lt;p&gt;The debug mode loads per-edge data (speed, density, road class, costs) so you can see exactly why the engine chose one road over another. The inspect endpoint visualizes tile-level node and edge data for when things really go wrong.&lt;/p&gt;
&lt;h2 id="criterion-benchmarks"&gt;Criterion benchmarks&lt;/h2&gt;
&lt;p&gt;Benchmarks run against France tiles across four crates:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Tiles&lt;/strong&gt;: cache hit (~20ns thread-local), cold load (mmap), edge traversal&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Costing&lt;/strong&gt;: single edge cost, transition cost, access check&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Routing&lt;/strong&gt;: 5km urban, 55km regional, 240km long-distance&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Matrix&lt;/strong&gt;: 1x1, 4x4 coastal, 9x9 urban&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A separate bench runner does HTTP-level comparison against Valhalla, reporting p50/p95/p99 latencies and route cost/distance diffs. Useful for regression testing and for validating that the cost model matches Valhalla&amp;rsquo;s output within acceptable margins.&lt;/p&gt;
&lt;h2 id="where-it-stands"&gt;Where it stands&lt;/h2&gt;
&lt;p&gt;The engine handles France-scale data with three costing profiles. Routes match Valhalla on the scenarios I&amp;rsquo;ve tested. Turn-by-turn directions work in 34 languages. The matrix scales to 9x9 with A* heuristics. Performance is good enough for a single-server deployment.&lt;/p&gt;
&lt;p&gt;What&amp;rsquo;s new since part 1:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Bicycle and pedestrian cost models&lt;/li&gt;
&lt;li&gt;Memory-mapped tiles with thread-local cache&lt;/li&gt;
&lt;li&gt;FxHash everywhere, DistanceApproximator for snapping (26% faster)&lt;/li&gt;
&lt;li&gt;Distance-adaptive hierarchy limits&lt;/li&gt;
&lt;li&gt;A* heuristics in the cost matrix&lt;/li&gt;
&lt;li&gt;Turn-by-turn maneuvers with localized narrative in 34 languages&lt;/li&gt;
&lt;li&gt;Multi-tile edge snapping&lt;/li&gt;
&lt;li&gt;Criterion benchmarks and a Valhalla comparison webapp&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;What&amp;rsquo;s still ahead:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Isochrones&lt;/strong&gt;: reachability polygons from a point within a time/distance budget&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Contraction hierarchies&lt;/strong&gt;: the hierarchy transitions help but true CH preprocessing would unlock cross-Europe routes at acceptable latency&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Via points&lt;/strong&gt;: multi-leg routes with intermediate stops&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Building a routing engine in Rust</title><link>https://man-you.ringum.net/posts/calculon/</link><pubDate>Sat, 14 Feb 2026 10:00:00 +0200</pubDate><guid>https://man-you.ringum.net/posts/calculon/</guid><description>&lt;p&gt;&lt;figure&gt;
&lt;img src="https://man-you.ringum.net/posts/calculon/calculon.svg" alt="Calculon" /&gt;
&lt;figcaption&gt;Why can&amp;#39;t I move on?&lt;/figcaption&gt;
&lt;/figure&gt;&lt;/p&gt;
&lt;p&gt;A routing engine in Rust. Bidirectional A*, many-to-many distance matrices, Valhalla-compatible API. Six crates, about 8,000 lines.&lt;/p&gt;
&lt;h2 id="why"&gt;Why&lt;/h2&gt;
&lt;p&gt;We use Valhalla for routing and distance matrices at Woosmap. It works most of the time. When it doesn&amp;rsquo;t, we&amp;rsquo;re stuck. We treat it as a black box: file an issue, upgrade, hope the next release fixes things. When the matrix returns wrong costs for certain edge cases or the engine routes you through someone&amp;rsquo;s driveway, there&amp;rsquo;s no realistic way for us to dig in. It&amp;rsquo;s a massive C++ codebase that assumes you&amp;rsquo;ve been living in it for years.&lt;/p&gt;
&lt;p&gt;The goal is to replace Valhalla entirely for our use case: routing, distance matrices, and isochrones, with driving, cycling, and walking profiles. Not fork it, not wrap it. Rewrite the parts we need in Rust so we can actually own the thing.&lt;/p&gt;
&lt;h2 id="architecture"&gt;Architecture&lt;/h2&gt;
&lt;p&gt;Six crates in a Cargo workspace:&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;calculon/
├── calculon-core # Foundation types: coordinates, graph IDs, constants
├── calculon-tiles # Binary tile reader: nodes, edges, transitions
├── calculon-costing # Cost models: auto/driving with turn penalties
├── calculon-route # Bidirectional A* pathfinding
├── calculon-matrix # Many-to-many distance matrix
└── calculon # HTTP server: Axum handlers, Valhalla-compatible API&lt;/code&gt;&lt;/pre&gt;
&lt;/figure&gt;
&lt;p&gt;The HTTP layer at the top is thin: parse JSON, call the algorithm, format the response. Everything interesting happens in the middle crates.&lt;/p&gt;
&lt;h2 id="tile-based-graph"&gt;Tile-based graph&lt;/h2&gt;
&lt;p&gt;The road network lives in binary tiles, same format Valhalla uses. Nodes are intersections, directed edges are road segments, edge info holds geometry and names, node transitions connect tiles to each other.&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;figcaption&gt;crates/calculon-tiles/src/graph_tile.rs&lt;/figcaption&gt;
&lt;div class="highlight" title="crates/calculon-tiles/src/graph_tile.rs"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-rust" data-lang="rust"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;pub&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;from_bytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;: &lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;u8&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-&amp;gt; &lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="bp"&gt;Self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;TileError&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;len&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;size_of&lt;/span&gt;::&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;GraphTileHeader&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;Err&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TileError&lt;/span&gt;::&lt;span class="n"&gt;TooSmall&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;: &lt;span class="nc"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;min&lt;/span&gt;: &lt;span class="nc"&gt;size_of&lt;/span&gt;::&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;GraphTileHeader&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;header&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;unsafe&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;*&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;as_ptr&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;GraphTileHeader&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;header&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;end_offset&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;usize&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;len&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;Err&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TileError&lt;/span&gt;::&lt;span class="n"&gt;SizeMismatch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;header_size&lt;/span&gt;: &lt;span class="nc"&gt;header&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;end_offset&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;usize&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;actual_size&lt;/span&gt;: &lt;span class="nc"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;base_ll&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;header&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;base_ll&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;GraphTile&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;base_ll&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt;/// Get the node at a given index.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;pub&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;node&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;: &lt;span class="kt"&gt;u32&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-&amp;gt; &lt;span class="nb"&gt;Option&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;NodeInfo&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;header&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;nodecount&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;None&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;offset&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;size_of&lt;/span&gt;::&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;GraphTileHeader&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;usize&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;size_of&lt;/span&gt;::&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;NodeInfo&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;unsafe&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;*&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;offset&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;as_ptr&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;NodeInfo&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;No deserialization, just pointer casting into the raw bytes. The tile is a flat buffer with a header followed by fixed-size node and edge records at known offsets. Fast, but the crate refuses to compile on big-endian targets because the byte layout has to match.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;GraphReader&lt;/code&gt; sits on top of an LRU cache. Algorithm needs an edge in some tile? Check the cache, load the file if it&amp;rsquo;s not there, hand back an &lt;code&gt;Arc&amp;lt;GraphTile&amp;gt;&lt;/code&gt;. Default cache holds 200 tiles, which covers most regional queries without touching disk twice.&lt;/p&gt;
&lt;p&gt;Reusing Valhalla&amp;rsquo;s tile format was a big shortcut. Designing a graph format from scratch would have been a project in itself.&lt;/p&gt;
&lt;h2 id="cost-model"&gt;Cost model&lt;/h2&gt;
&lt;p&gt;This is where routing gets interesting, and where most bugs hide. The cost model decides how expensive each road segment is to traverse. Not just distance. Time, comfort, how annoying a particular turn is. The values here are derived from Valhalla&amp;rsquo;s own cost model. No point reinventing what they&amp;rsquo;ve already calibrated.&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;figcaption&gt;crates/calculon-costing/src/auto_cost.rs&lt;/figcaption&gt;
&lt;div class="highlight" title="crates/calculon-costing/src/auto_cost.rs"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-rust" data-lang="rust"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;RIGHT_SIDE_TURN_COSTS&lt;/span&gt;: &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;f32&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// Straight
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.75&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// SlightRight
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// Right (favorable)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// SharpRight (favorable-sharp)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;9.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// Reverse
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;3.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// SharpLeft (unfavorable-sharp)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;2.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// Left (unfavorable, crosses oncoming traffic)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.75&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// SlightLeft
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;];&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;LEFT_SIDE_TURN_COSTS&lt;/span&gt;: &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;f32&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// Straight
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.75&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// SlightRight
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;2.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// Right (unfavorable)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;3.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// SharpRight (unfavorable-sharp)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;9.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// Reverse
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// SharpLeft (favorable-sharp)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// Left (favorable)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.75&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// SlightLeft
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;];&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;Two tables: one for right-side driving countries (France, US), one for left-side (UK, Japan). In France, a left turn crosses oncoming traffic so it costs more. In the UK, it&amp;rsquo;s the right turn. Get this wrong and the engine starts suggesting weird U-turn loops to avoid a simple left.&lt;/p&gt;
&lt;p&gt;The rest of the cost model:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Speed factors: pre-computed &lt;code&gt;3.6 / speed_kph&lt;/code&gt; lookup table, seconds per meter at each speed&lt;/li&gt;
&lt;li&gt;Highway preference: a slider from &lt;code&gt;-1.0&lt;/code&gt; to &lt;code&gt;1.0&lt;/code&gt;, push it high and the engine prefers motorways&lt;/li&gt;
&lt;li&gt;Surface penalties: gravel costs more than asphalt&lt;/li&gt;
&lt;li&gt;Intersection density: dense urban intersections add delay, factors range from 1.0 up to 3.5&lt;/li&gt;
&lt;li&gt;Toll booths: flat time penalty per crossing&lt;/li&gt;
&lt;li&gt;Traffic signals: extra delay at signalized intersections&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All of this is pre-computed into lookup tables at startup. During search, computing the cost of an edge is just array indexing.&lt;/p&gt;
&lt;p&gt;The algorithm finds &lt;em&gt;a&lt;/em&gt; shortest path. The cost model decides whether that path makes any sense to a human driver. Same constants Valhalla uses, but now in code we can read and step through when something looks off.&lt;/p&gt;
&lt;h2 id="bidirectional-a"&gt;Bidirectional A*&lt;/h2&gt;
&lt;p&gt;Two search trees grow at the same time: one forward from the origin, one backward from the destination. They meet somewhere in the middle.&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;figcaption&gt;crates/calculon-route/src/bidirectional_astar.rs&lt;/figcaption&gt;
&lt;div class="highlight" title="crates/calculon-route/src/bidirectional_astar.rs"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-rust" data-lang="rust"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;pub&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="nc"&gt;BidirectionalAStar&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;edgelabels_forward&lt;/span&gt;: &lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;BDEdgeLabel&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;edgelabels_reverse&lt;/span&gt;: &lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;BDEdgeLabel&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;adjacencylist_forward&lt;/span&gt;: &lt;span class="nc"&gt;DoubleBucketQueue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;adjacencylist_reverse&lt;/span&gt;: &lt;span class="nc"&gt;DoubleBucketQueue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;edgestatus_forward&lt;/span&gt;: &lt;span class="nc"&gt;EdgeStatus&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;edgestatus_reverse&lt;/span&gt;: &lt;span class="nc"&gt;EdgeStatus&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;heuristic_forward&lt;/span&gt;: &lt;span class="nc"&gt;AStarHeuristic&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;heuristic_reverse&lt;/span&gt;: &lt;span class="nc"&gt;AStarHeuristic&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cost_diff&lt;/span&gt;: &lt;span class="kt"&gt;f32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cost_threshold&lt;/span&gt;: &lt;span class="kt"&gt;f32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;best_connections&lt;/span&gt;: &lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;CandidateConnection&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// ...
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;Everything is doubled: edge labels, priority queues, edge status, heuristics. The heuristic is geographic distance to the opposite endpoint. Simple, admissible, good enough.&lt;/p&gt;
&lt;p&gt;The priority queue is a double-bucket structure: coarse outer buckets, fine inner buckets. Turns out a binary heap isn&amp;rsquo;t great for routing because the cost values cluster in narrow ranges. Buckets handle that better.&lt;/p&gt;
&lt;p&gt;The search stops when the best connection found so far, plus a 420-second buffer, exceeds the cheapest item in either queue. That buffer lets it keep looking for slightly better paths after the first connection without searching forever.&lt;/p&gt;
&lt;p&gt;Hierarchy transitions help a lot. Near the endpoints, the search considers local roads. As it moves away, it &amp;ldquo;jumps up&amp;rdquo; to highways and trunk roads. In the middle of a long route, there&amp;rsquo;s no point expanding residential streets. This cuts the number of edges explored dramatically.&lt;/p&gt;
&lt;h2 id="many-to-many-matrix"&gt;Many-to-many matrix&lt;/h2&gt;
&lt;p&gt;A 50x50 distance matrix done naively (individual route for each pair) means 2,500 searches. That&amp;rsquo;s not going to work.&lt;/p&gt;
&lt;p&gt;The trick: expand all sources forward simultaneously, all targets backward simultaneously, and watch for collisions. When source &lt;code&gt;i&lt;/code&gt;&amp;rsquo;s forward search reaches an edge that target &lt;code&gt;j&lt;/code&gt;&amp;rsquo;s reverse search already touched, that&amp;rsquo;s a connection.&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;figcaption&gt;crates/calculon-matrix/src/cost_matrix.rs&lt;/figcaption&gt;
&lt;div class="highlight" title="crates/calculon-matrix/src/cost_matrix.rs"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-rust" data-lang="rust"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;pub&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="nc"&gt;CostMatrix&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;edgelabels_forward&lt;/span&gt;: &lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;BDEdgeLabel&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;queues_forward&lt;/span&gt;: &lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;DoubleBucketQueue&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;edgestatus_forward&lt;/span&gt;: &lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;EdgeStatus&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;edgelabels_reverse&lt;/span&gt;: &lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;BDEdgeLabel&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// ...
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;Each source and target gets its own labels, queue, and status set, but they all walk the same graph. A &lt;code&gt;ReachedMap&lt;/code&gt; (basically a &lt;code&gt;HashMap&amp;lt;edge_id, Vec&amp;lt;location_index&amp;gt;&amp;gt;&lt;/code&gt;) tracks who&amp;rsquo;s been where. When a forward expansion hits an edge in the reverse map, we record the connection cost.&lt;/p&gt;
&lt;p&gt;Result is an N*M grid of time/distance values. Some cells might be unreachable. The whole thing caps at 2 million iterations so it doesn&amp;rsquo;t run forever on disconnected graphs.&lt;/p&gt;
&lt;h2 id="http-server"&gt;HTTP server&lt;/h2&gt;
&lt;p&gt;Three endpoints on Axum, matching Valhalla&amp;rsquo;s JSON format so existing clients can point at Calculon without code changes.&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;figcaption&gt;crates/calculon/src/server.rs&lt;/figcaption&gt;
&lt;div class="highlight" title="crates/calculon/src/server.rs"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-rust" data-lang="rust"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;pub&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;build_router&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;: &lt;span class="nc"&gt;Arc&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;AppState&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-&amp;gt; &lt;span class="nc"&gt;Router&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Router&lt;/span&gt;::&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;/route&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;handler_route&lt;/span&gt;::&lt;span class="n"&gt;handle_route&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;/sources_to_targets&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;handler_matrix&lt;/span&gt;::&lt;span class="n"&gt;handle_matrix&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;/status&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;handler_status&lt;/span&gt;::&lt;span class="n"&gt;handle_status&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;layer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CorsLayer&lt;/span&gt;::&lt;span class="n"&gt;permissive&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;with_state&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;Route and matrix searches are CPU-bound: they can block for hundreds of milliseconds on a big query. They run on Tokio&amp;rsquo;s blocking thread pool via &lt;code&gt;spawn_blocking&lt;/code&gt; so they don&amp;rsquo;t starve the async runtime.&lt;/p&gt;
&lt;p&gt;Each request gets a &lt;code&gt;CancelGuard&lt;/code&gt; backed by an &lt;code&gt;AtomicBool&lt;/code&gt;. Client disconnects? The guard drops, sets the flag, and the algorithm checks it periodically and bails out. Seemed like overkill at first, but it matters when someone fires off a matrix request and immediately refreshes the page.&lt;/p&gt;
&lt;p&gt;A route request:&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;locations&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nt"&gt;&amp;#34;lat&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;43.731&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;lon&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;7.419&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nt"&gt;&amp;#34;lat&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;43.696&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;lon&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;7.271&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;costing&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;auto&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;costing_options&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nt"&gt;&amp;#34;auto&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nt"&gt;&amp;#34;use_highways&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.8&lt;/span&gt;&lt;span class="p"&gt;}},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;units&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;kilometers&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;Response comes back with a polyline6-encoded shape, total time, distance, bounding box, and some basic maneuvers (though the maneuvers are stubs for now, just &amp;ldquo;Start&amp;rdquo; and &amp;ldquo;Arrive&amp;rdquo;).&lt;/p&gt;
&lt;h2 id="where-it-stands"&gt;Where it stands&lt;/h2&gt;
&lt;p&gt;Driving works. Routes match Valhalla&amp;rsquo;s output on the Monaco dataset. The matrix handles concurrent requests with cancellation. The server drops in behind the same API.&lt;/p&gt;
&lt;p&gt;What&amp;rsquo;s there:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Driving cost model with turns, highways, surface, density, tolls&lt;/li&gt;
&lt;li&gt;Bidirectional A* with hierarchy transitions&lt;/li&gt;
&lt;li&gt;Many-to-many matrix via simultaneous bidirectional Dijkstra&lt;/li&gt;
&lt;li&gt;Valhalla-compatible REST API&lt;/li&gt;
&lt;li&gt;LRU tile caching with memory-mapped I/O&lt;/li&gt;
&lt;li&gt;Cooperative request cancellation&lt;/li&gt;
&lt;li&gt;Tests against Monaco dataset&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;What&amp;rsquo;s left to reach parity:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Cycling and walking profiles&lt;/strong&gt;: the cost model is structured for it, but only driving is implemented so far.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Isochrones&lt;/strong&gt;: reachability polygons from a point within a time/distance budget. The search infrastructure is there, the polygon generation isn&amp;rsquo;t.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Turn-by-turn directions&lt;/strong&gt;: maneuvers are stubs. Real maneuver generation needs street names, instruction templates, sign data.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Contraction hierarchies&lt;/strong&gt;: the A* is fine for regional routes but CH would be needed for cross-country at acceptable latency.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Multi-leg routes&lt;/strong&gt;: only origin to destination, no via points yet.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;About 8,000 lines across six crates. The driving case works end to end. Cycling, walking, and isochrones are what&amp;rsquo;s between this and actually replacing Valhalla.&lt;/p&gt;</description></item><item><title>Scaling Freddie to 10m landcover</title><link>https://man-you.ringum.net/posts/scaling-freddie-10m/</link><pubDate>Tue, 10 Feb 2026 12:00:00 +0200</pubDate><guid>https://man-you.ringum.net/posts/scaling-freddie-10m/</guid><description>&lt;p&gt;Serving vector tiles from 10-meter resolution land cover data. What breaks when you 100x the input resolution, and how topology-preserving smoothing fixes the ugly parts.&lt;/p&gt;
&lt;h2 id="background"&gt;Background&lt;/h2&gt;
&lt;p&gt;Freddie is our tile server for land cover data. It reads GeoTIFF raster files (pixels classified as wood, grass, built-up, water, etc.) and serves them as vector tiles or raster tiles over HTTP. Nothing fancy. Read pixels, make shapes, encode, serve.&lt;/p&gt;
&lt;p&gt;The original setup used ESA WorldCover at ~100m resolution. Native resolution sits around z8 in Web Mercator, so serving z0–z8 tiles was straightforward. Sample pixels, run-length encode, merge adjacent faces of the same class, encode to MVT. Done. It just worked, and I didn&amp;rsquo;t think much about it.&lt;/p&gt;
&lt;p&gt;Then we switched to the 10m dataset. Native resolution jumps to ~z13–z14. The source TIFFs balloon to 36,000 x 36,000 pixels each. Everything that worked at 100m either broke or looked terrible. This is the story of fixing all of it.&lt;/p&gt;
&lt;h2 id="what-breaks"&gt;What breaks&lt;/h2&gt;
&lt;p&gt;Three problems, in order of how quickly you notice them.&lt;/p&gt;
&lt;h3 id="staircase-boundaries"&gt;Staircase boundaries&lt;/h3&gt;
&lt;p&gt;At zoom levels above native resolution (z15, z16), each source pixel stretches across 4–16 tile pixels. The vector boundaries follow pixel edges exactly: 90-degree staircase patterns everywhere. At 100m this was never visible because nobody zoomed past z8. At 10m you can zoom to z16 and the pixel grid is right there, staring back at you. Minecraft terrain, basically.&lt;/p&gt;
&lt;h3 id="performance-at-low-zoom"&gt;Performance at low zoom&lt;/h3&gt;
&lt;p&gt;A z0 tile covers the entire world. With a 36,000 x 36,000 source TIFF, generating that tile means sampling millions of pixels. At 100m the source was small enough that brute force worked. At 10m, a single z0 request could take seconds. I watched the first one come back and thought the server had hung.&lt;/p&gt;
&lt;h3 id="topology-seams"&gt;Topology seams&lt;/h3&gt;
&lt;p&gt;The naive fix for staircases (smooth each polygon independently) creates gaps and overlaps between adjacent regions. Two polygons that share a boundary get smoothed in different directions. You end up with visible seams, z-fighting, or missing pixels at class boundaries. Fix one problem, create another. Classic.&lt;/p&gt;
&lt;h2 id="cog-multi-resolution"&gt;COG multi-resolution&lt;/h2&gt;
&lt;p&gt;The performance problem, at least, has a clean solution: Cloud Optimized GeoTIFF.&lt;/p&gt;
&lt;p&gt;A COG file stores the full-resolution image plus downsampled overviews, typically at 2x, 4x, 8x, 16x, 32x reductions. Instead of one 36,000 x 36,000 image, you get a pyramid:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Level&lt;/th&gt;
&lt;th&gt;Size&lt;/th&gt;
&lt;th&gt;Use for&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;36,000 x 36,000&lt;/td&gt;
&lt;td&gt;z10+&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;18,000 x 18,000&lt;/td&gt;
&lt;td&gt;z8–z9&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;9,000 x 9,000&lt;/td&gt;
&lt;td&gt;z6–z7&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;4,500 x 4,500&lt;/td&gt;
&lt;td&gt;z4–z5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;2,250 x 2,250&lt;/td&gt;
&lt;td&gt;z2–z3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;1,125 x 1,125&lt;/td&gt;
&lt;td&gt;z0–z1&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The heuristic is simple: &lt;code&gt;levelIndex = (maxZoom - zoom) / 2&lt;/code&gt;. A z0 tile now samples from a 1,125 x 1,125 overview instead of the full image. About 1,000x less data to read.&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;figcaption&gt;Resolution level selection&lt;/figcaption&gt;
&lt;div class="highlight" title="Resolution level selection"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;TiffCollection&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;selectLevel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;zoom&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;levelIndex&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;maxZoom&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;zoom&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;levelIndex&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;levels&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;levelIndex&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;levels&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;levelIndex&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;The &lt;code&gt;TiffCollection&lt;/code&gt; manages multiple TIFF files with spatial indexing: a 3 x 3 degree grid for fast lookups. At tile boundaries where multiple TIFFs overlap, deterministic ordering ensures consistent output.&lt;/p&gt;
&lt;p&gt;z0 tiles went from seconds to milliseconds. Problem one down, two to go.&lt;/p&gt;
&lt;h2 id="the-smoothing-problem"&gt;The smoothing problem&lt;/h2&gt;
&lt;p&gt;With COG handling performance, the real challenge is visual quality. Run-length encoding produces pixel-aligned polygons. At native resolution that&amp;rsquo;s fine: the boundaries are detailed enough. But zoom in past native and you see the grid. It&amp;rsquo;s ugly, and no amount of squinting makes it acceptable.&lt;/p&gt;
&lt;p&gt;Two complementary approaches: smooth the raster before vectorizing, or smooth the vectors after. I ended up doing both.&lt;/p&gt;
&lt;h3 id="sdf-raster-filtering"&gt;SDF raster filtering&lt;/h3&gt;
&lt;p&gt;Signed Distance Fields are typically used in font rendering. Valve popularized them for GPU text back in 2007. Turns out the same idea applies beautifully to classified rasters.&lt;/p&gt;
&lt;p&gt;For each class in the tile:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Compute a distance transform (Meijster algorithm, O(n) per dimension): every pixel gets its Euclidean distance to the nearest boundary&lt;/li&gt;
&lt;li&gt;Sign it: positive inside the class region, negative outside&lt;/li&gt;
&lt;li&gt;Gaussian blur the SDF&lt;/li&gt;
&lt;li&gt;Reclassify pixels based on the blurred SDF values&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Blurring an SDF instead of blurring raw pixel values is the key insight. Raw blur smears class boundaries into garbage. SDF blur produces smooth, continuous boundaries that still snap cleanly to class assignments.&lt;/p&gt;
&lt;p&gt;The blur radius scales with the upscale factor:&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;z14 (native, 1x): blur = 6.0 source pixels
z15 (2x upscale): blur = 3.0 source pixels
z16 (4x upscale): blur = 1.5 source pixels
z17+ (8x+): clamped to 1.0&lt;/code&gt;&lt;/pre&gt;
&lt;/figure&gt;
&lt;p&gt;A 5 x 5 mode filter can run before SDF to remove isolated single-pixel noise, common in land cover data where a lone &amp;ldquo;built-up&amp;rdquo; pixel appears in a forest.&lt;/p&gt;
&lt;h3 id="barbapapa-topology-preserving-vector-smoothing"&gt;Barbapapa: topology-preserving vector smoothing&lt;/h3&gt;
&lt;p&gt;SDF smoothing handles the raster level. But even with pre-smoothed rasters, the vectorized output still has pixel-aligned vertices. Near native resolution, where SDF barely does anything, the staircases are still there. We need vector-level smoothing too.&lt;/p&gt;
&lt;p&gt;The fundamental problem: polygons that share a boundary must be smoothed identically along that boundary. Smooth them independently and you get gaps and overlaps. I tried independent smoothing first. It looked like the polygons were trying to escape from each other.&lt;/p&gt;
&lt;p&gt;The solution uses the half-edge data structure (DCEL) that Freddie already maintains for face merging. Adjacent polygons share vertex references through twin half-edges. Move a shared vertex and it moves for both polygons simultaneously. The topology enforces consistency.&lt;/p&gt;
&lt;p&gt;I named the algorithm Barbapapa, after the cartoon character that can reshape into anything while staying the same blob. The algorithm has five stages:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1. Vertex deduplication.&lt;/strong&gt; After face merging, some vertices at identical coordinates are separate objects. Canonicalize them so shared boundaries reference the same vertex instance.&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;figcaption&gt;Vertex deduplication&lt;/figcaption&gt;
&lt;div class="highlight" title="Vertex deduplication"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;canonical&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;vertex&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;range&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;allVertices&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;X&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Y&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;existing&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;canonical&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// Repoint all half-edges from v to existing&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;replaceVertex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;existing&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;canonical&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;&lt;strong&gt;2. Edge subdivision.&lt;/strong&gt; Laplacian smoothing can only move existing vertices: it can&amp;rsquo;t add curvature where there are no points. Subdivide edges by inserting midpoints, spaced at ~3.5 source pixels apart. Both sides of a shared edge (the half-edge and its twin) get subdivided identically.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3. Identify immovable vertices.&lt;/strong&gt; Tile boundary vertices must not move (adjacent tiles need matching edges). Vertices that exist only on hole boundaries are also pinned (I&amp;rsquo;ll get to why that caveat matters in a moment).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;4. Laplacian smoothing.&lt;/strong&gt; For each movable interior vertex, compute the centroid of its neighbors and blend:&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;new_position = (1 - alpha) * current + alpha * centroid&lt;/code&gt;&lt;/pre&gt;
&lt;/figure&gt;
&lt;p&gt;Jacobi-style: compute all new positions first, then apply all updates simultaneously. This avoids order-dependent results. Three to four iterations with alpha = 0.5–0.6 produces good results.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;5. Collision prevention.&lt;/strong&gt; Ensure no vertex collapses onto another. Sorted vertex iteration guarantees deterministic output.&lt;/p&gt;
&lt;p&gt;The algorithm overhead is under 5% of total tile generation time. Most time is still spent on TIFF reading and face merging. Good: the smoothing is essentially free.&lt;/p&gt;
&lt;h3 id="the-hole-vertex-problem"&gt;The hole vertex problem&lt;/h3&gt;
&lt;p&gt;Version 1 of Barbapapa had a subtle bug that killed 93% of its effectiveness. I shipped it thinking it was working. It was barely doing anything.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s the setup: small classified regions (a pond in a forest, a building in a field) appear both as a face (the pond) and as a hole in the surrounding face (the forest). The vertices on these boundaries are shared between the face&amp;rsquo;s outer ring and the surrounding face&amp;rsquo;s hole ring.&lt;/p&gt;
&lt;p&gt;My original code marked all hole-boundary vertices as immovable. The reasoning seemed sound: holes have specific shapes that shouldn&amp;rsquo;t be distorted.&lt;/p&gt;
&lt;p&gt;The problem: a vertex on a small face&amp;rsquo;s outer boundary is &lt;em&gt;also&lt;/em&gt; on the surrounding face&amp;rsquo;s hole boundary. So marking all hole vertices as immovable pinned almost every interior vertex. In test datasets, 110 out of 118 interior faces had zero smoothing applied. The algorithm was running, doing math, producing output, and changing almost nothing. I only caught it when I added per-face metrics and saw the numbers.&lt;/p&gt;
&lt;p&gt;The fix: only pin vertices that appear &lt;em&gt;exclusively&lt;/em&gt; on hole boundaries and never on any face&amp;rsquo;s outer boundary. After the fix, unsmoothed interior faces dropped from 110 to 8, the remaining 8 being sub-pixel features too small to matter. Suddenly Barbapapa actually worked.&lt;/p&gt;
&lt;h3 id="zoom-adaptive-parameters"&gt;Zoom-adaptive parameters&lt;/h3&gt;
&lt;p&gt;Smoothing needs vary wildly across zoom levels. At z14 (native resolution) the pixels are small, so light smoothing is fine. At z16 (4x upscale) the pixel grid is screaming at you, so you need to be aggressive. Below native, the data is already downsampled and naturally smooth.&lt;/p&gt;
&lt;p&gt;Rather than hardcoding per-zoom parameters, everything scales with &lt;code&gt;sourcePixelSize&lt;/code&gt;: how many tile pixels each source pixel occupies:&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;figcaption&gt;Adaptive smoothing parameters&lt;/figcaption&gt;
&lt;div class="highlight" title="Adaptive smoothing parameters"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;GetEffectiveParams&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sourcePixelSize&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;float64&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;iterations&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;alpha&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;float64&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;sourcePixelSize&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// Below native: scale down proportionally&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;scale&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;sourcePixelSize&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;3.0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;scale&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;scale&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// Above native: boost with log curve, cap at reasonable limits&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;iterBoost&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Log2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sourcePixelSize&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.75&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;alphaBoost&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Log2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sourcePixelSize&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.05&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;3.0&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="nx"&gt;iterBoost&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="nx"&gt;alphaBoost&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;Using pixel size directly instead of zoom level means the same code works regardless of source resolution. Swap in a different dataset and the smoothing adapts automatically. One less thing to tune by hand.&lt;/p&gt;
&lt;h2 id="pipeline-system"&gt;Pipeline system&lt;/h2&gt;
&lt;p&gt;With multiple raster filters and vector simplifiers, the number of possible combinations grew fast. I was adding flags to the server every other day (&lt;code&gt;--sdf&lt;/code&gt;, &lt;code&gt;--mode-filter&lt;/code&gt;, &lt;code&gt;--barbapapa-iterations&lt;/code&gt;, &lt;code&gt;--sdf-blur-radius&lt;/code&gt;) and it was getting out of hand. So I built a pipeline system instead.&lt;/p&gt;
&lt;p&gt;A pipeline is a named sequence of processing steps:&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;TIFF Data → [Raster Filters] → Vectorization → [Vector Simplifiers] → MVT&lt;/code&gt;&lt;/pre&gt;
&lt;/figure&gt;
&lt;p&gt;Built-in pipelines cover common configurations:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Pipeline&lt;/th&gt;
&lt;th&gt;Raster filters&lt;/th&gt;
&lt;th&gt;Vector simplifiers&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;raw&lt;/td&gt;
&lt;td&gt;none&lt;/td&gt;
&lt;td&gt;SimplifyFaces&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;default&lt;/td&gt;
&lt;td&gt;none&lt;/td&gt;
&lt;td&gt;SimplifyFaces, Barbapapa&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;smooth-sdf&lt;/td&gt;
&lt;td&gt;SDF&lt;/td&gt;
&lt;td&gt;SimplifyFaces, Barbapapa&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;smooth-mode5x5&lt;/td&gt;
&lt;td&gt;Mode 5x5&lt;/td&gt;
&lt;td&gt;SimplifyFaces, Barbapapa&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;smooth-combined&lt;/td&gt;
&lt;td&gt;Mode 5x5, SDF&lt;/td&gt;
&lt;td&gt;SimplifyFaces, Barbapapa&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;aggressive&lt;/td&gt;
&lt;td&gt;Mode 5x5, SDF (8px)&lt;/td&gt;
&lt;td&gt;SimplifyFaces, Barbapapa (8 iter), Douglas-Peucker&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Each step collects metrics (timing, face counts, vertex counts) so you can see exactly where time is spent. This turned out to be invaluable for debugging: when a tile looks wrong, you can trace exactly which step mangled it.&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;figcaption&gt;Running with a pipeline&lt;/figcaption&gt;
&lt;div class="highlight" title="Running with a pipeline"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;./freddie-server --pipeline smooth-combined&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;One flag instead of six. Custom pipelines can still be composed from individual filters if you want to experiment.&lt;/p&gt;
&lt;h2 id="determinism"&gt;Determinism&lt;/h2&gt;
&lt;p&gt;This one bit me late. Go maps have non-deterministic iteration order. It&amp;rsquo;s by design, to prevent people from depending on it. When iterating over faces to apply smoothing, different runs could produce different vertex orderings, leading to different (though equally valid) output. Visually identical, but different bytes.&lt;/p&gt;
&lt;p&gt;For tile caching that&amp;rsquo;s a disaster. You want the same input to always produce the exact same bytes, otherwise your cache never hits. The fix: an &lt;code&gt;OrderedFaceMap&lt;/code&gt; that sorts keys once at construction and iterates in sorted order. Combined with Jacobi-style (simultaneous) updates in Barbapapa, the output is now bit-for-bit reproducible. Run it twice, get the same file. Should have been obvious from the start, but Go&amp;rsquo;s map randomization is the kind of thing you forget about until it ruins your afternoon.&lt;/p&gt;
&lt;h2 id="performance"&gt;Performance&lt;/h2&gt;
&lt;p&gt;Numbers matter, so here they are. Benchmarks on M1 Max, single tile at z8:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Operation&lt;/th&gt;
&lt;th&gt;Time&lt;/th&gt;
&lt;th&gt;Memory&lt;/th&gt;
&lt;th&gt;Allocations&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;GetWebMercatorTileMVT&lt;/td&gt;
&lt;td&gt;345ms&lt;/td&gt;
&lt;td&gt;152MB&lt;/td&gt;
&lt;td&gt;599k&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GetBitmapTileWithMargins&lt;/td&gt;
&lt;td&gt;259ms&lt;/td&gt;
&lt;td&gt;117MB&lt;/td&gt;
&lt;td&gt;10k&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GetWebMercatorTilePNG&lt;/td&gt;
&lt;td&gt;247ms&lt;/td&gt;
&lt;td&gt;113MB&lt;/td&gt;
&lt;td&gt;10k&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;With 10 workers: ~35ms per tile, ~286 tiles/second. A full z0–z8 pyramid (21,845 tiles) generates in about 90 seconds.&lt;/p&gt;
&lt;p&gt;I built a benchmark harness that runs multiple pipelines across multiple regions and zoom levels, spitting out per-tile metrics and SVG/PNG outputs for visual comparison. Change a value, regenerate, compare. Without this I would have been guessing at parameter values forever. The difference between &amp;ldquo;looks right&amp;rdquo; and &amp;ldquo;is right&amp;rdquo; is hard to eyeball across thousands of tiles.&lt;/p&gt;
&lt;h2 id="the-algorithms-evolution"&gt;The algorithm&amp;rsquo;s evolution&lt;/h2&gt;
&lt;p&gt;Barbapapa went through seven revisions. Each one was driven by a specific visual artifact I found while staring at tiles, the kind of bug where you zoom into some random patch of Indonesia and go &amp;ldquo;wait, what is &lt;em&gt;that&lt;/em&gt;.&amp;rdquo;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;v1.0&lt;/strong&gt;: Basic Laplacian smoothing. Worked, but barely visible. I thought the whole approach was a dead end.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;v1.1&lt;/strong&gt;: Edge subdivision. Suddenly there were actual curves instead of straight lines. The moment it clicked.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;v1.2&lt;/strong&gt;: Vertex deduplication. Enabled cross-face consistency. Shared boundaries finally moved together.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;v1.3&lt;/strong&gt;: Linked twin subdivision. Topological correctness for shared edges, fixing the cases where v1.2 missed a link.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;v1.4&lt;/strong&gt;: Zero-length edge prevention. Eliminated degenerate geometry that made the MVT encoder choke.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;v2.0&lt;/strong&gt;: Hole-only vertex identification. The 93% fix. The algorithm finally did what it was supposed to do all along.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;v2.1&lt;/strong&gt;: Deterministic iteration. Reproducible output for caching, after Go&amp;rsquo;s map randomization burned me.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The benchmark harness made this iteration loop possible. Every revision got validated against the full test suite before shipping: no visual regressions, no degenerate geometry, no byte-level surprises.&lt;/p&gt;
&lt;h2 id="known-limitations"&gt;Known limitations&lt;/h2&gt;
&lt;p&gt;Two artifacts I decided to live with.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Fish-tail spikes at z14+.&lt;/strong&gt; Where two smoothing forces compete at narrow polygon tips, V-shaped spikes can appear. They&amp;rsquo;re small, they&amp;rsquo;re rare, and fixing them would mean adding a post-smoothing vertex cleanup pass that I don&amp;rsquo;t want to maintain.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Integer coordinate quantization.&lt;/strong&gt; MVT uses integer coordinates. Smoothed positions get rounded, which at very high zoom can produce slightly uneven curves. Floating-point vertices would fix it, but that&amp;rsquo;s a change that ripples through the entire encoding pipeline. Not worth it for an artifact you have to zoom to z17 to notice.&lt;/p&gt;
&lt;h2 id="what-changed-architecturally"&gt;What changed architecturally&lt;/h2&gt;
&lt;p&gt;Looking back, the 10m upgrade touched four areas of the codebase, and the interesting thing is how little of the original code it replaced:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Multi-resolution TIFF handling&lt;/strong&gt;: COG support with automatic level selection. The &lt;code&gt;TiffCollection&lt;/code&gt; manages spatial indexing across multiple files. This was the most &amp;ldquo;engineering&amp;rdquo; change: straightforward but essential.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Half-edge topology operations&lt;/strong&gt;: Safe edge chaining, twin linking, subdivision. Error handling for topology invariant violations. This is the foundation that makes Barbapapa possible, and the part I&amp;rsquo;m most proud of. Getting the DCEL right was hard, but once it worked, everything built on top of it was clean.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Pipeline system&lt;/strong&gt;: Composable raster and vector processing steps with metrics. Named pipelines for one-command configuration. Born out of flag fatigue.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Adaptive parameters&lt;/strong&gt;: Everything scales with &lt;code&gt;sourcePixelSize&lt;/code&gt; instead of zoom level. Swap the dataset and the system adapts. The kind of design decision that pays for itself the first time someone asks &amp;ldquo;can we try a different source?&amp;rdquo;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The core tile serving path (HTTP handler, TIFF sampling, face merging, MVT encoding) stayed the same. The new code wraps around it rather than replacing it. I like that.&lt;/p&gt;
&lt;h2 id="where-it-stands"&gt;Where it stands&lt;/h2&gt;
&lt;p&gt;Freddie serves 10m land cover data from z0 to z16. Low zoom tiles are fast thanks to COG overviews. High zoom tiles look smooth thanks to Barbapapa and optional SDF filtering. The pipeline system makes it easy to experiment with different filter combinations without touching server code.&lt;/p&gt;
&lt;p&gt;The 10m dataset covers the full globe at a resolution where you can see individual fields and city blocks. At 100m everything was green-or-gray blobs. At 10m you can tell a park from a garden.&lt;/p&gt;</description></item><item><title>Tree: a browser that remembers where you came from</title><link>https://man-you.ringum.net/posts/tree-browser/</link><pubDate>Tue, 10 Feb 2026 10:00:00 +0200</pubDate><guid>https://man-you.ringum.net/posts/tree-browser/</guid><description>&lt;p&gt;Every browser has tabs. Linear, flat, disposable. Open thirty of them researching one topic and they sit in a row with no indication that twelve of them came from the same Wikipedia article. Close the wrong one and the context is gone.&lt;/p&gt;
&lt;p&gt;Tree is a macOS browser that replaces tabs with a tree. Every page knows its parent. Cmd+Click a link and it becomes a child node of the current page. The sidebar shows the full hierarchy: research paths branch naturally, and you can always trace back to where you started.&lt;/p&gt;
&lt;h2 id="the-data-model"&gt;The data model&lt;/h2&gt;
&lt;p&gt;The core is a SwiftData model with a self-referential parent-child relationship:&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;figcaption&gt;Models/TreeNode.swift&lt;/figcaption&gt;
&lt;div class="highlight" title="Models/TreeNode.swift"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-swift" data-lang="swift"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="n"&gt;Model&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;final&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TreeNode&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;#&lt;/span&gt;&lt;span class="n"&gt;Unique&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;TreeNode&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;([&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nodeId&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;nodeId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;UUID&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;URL&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;faviconData&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Data&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Date&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;lastUsedAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Date&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;weight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Int&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;isPinned&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Bool&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;sortOrder&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Int&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="n"&gt;Relationship&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;inverse&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="n"&gt;TreeNode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;children&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;parent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;TreeNode&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="n"&gt;Relationship&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;deleteRule&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cascade&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;children&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;TreeNode&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;The cascade delete rule means pruning a node takes its entire subtree with it. No orphans. The &lt;code&gt;parent&lt;/code&gt;/&lt;code&gt;children&lt;/code&gt; relationship is bidirectional. SwiftData maintains both sides automatically through the &lt;code&gt;inverse&lt;/code&gt; parameter.&lt;/p&gt;
&lt;p&gt;Each node tracks a &lt;code&gt;weight&lt;/code&gt; that increments on every visit, and an &lt;code&gt;isPinned&lt;/code&gt; flag. These feed the search ranking:&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-swift" data-lang="swift"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;searchRank&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Int&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;weight&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;isPinned&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;Pinned nodes get a massive boost. Frequently visited pages float to the top. It&amp;rsquo;s a simple heuristic but it works: the pages you care about surface first.&lt;/p&gt;
&lt;h2 id="branching"&gt;Branching&lt;/h2&gt;
&lt;p&gt;The key interaction is branching: Cmd+Click a link and the browser creates a child node instead of navigating away. The parent shifts to the secondary panel, and the child becomes the primary:&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;figcaption&gt;State/NavigationState.swift&lt;/figcaption&gt;
&lt;div class="highlight" title="State/NavigationState.swift"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-swift" data-lang="swift"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;selectFromBranch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;_&lt;/span&gt; &lt;span class="n"&gt;child&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;TreeNode&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;secondaryNode&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;primaryNode&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;primaryNode&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;child&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;child&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;incrementWeight&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;updateWebViewVisibility&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;saveLastSession&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;This is where the tree structure pays off. You&amp;rsquo;re reading documentation, Cmd+Click a linked API reference, and now you see both side by side: the original page on the right, the reference on the left. The sidebar shows them as parent and child. Navigate back up the tree anytime with keyboard shortcuts.&lt;/p&gt;
&lt;p&gt;Pinned nodes take this further. When a node is pinned, &lt;em&gt;all&lt;/em&gt; navigation from it creates children, not just Cmd+Click. Pin your project&amp;rsquo;s docs landing page and every link you follow branches automatically, building a research tree without any extra effort.&lt;/p&gt;
&lt;p&gt;&lt;figure&gt;
&lt;img
src="https://man-you.ringum.net/posts/tree-browser/tree-split-view-pr-review_hu_1624f2b46f7edfda.webp"
srcset="https://man-you.ringum.net/posts/tree-browser/tree-split-view-pr-review_hu_1624f2b46f7edfda.webp 960w, https://man-you.ringum.net/posts/tree-browser/tree-split-view-pr-review_hu_4b38275eb84f0fb6.webp 3050w"
sizes="(max-width: 960px) 100vw, 960px"
alt="Split view showing a PR and its CI checks side by side, with the browsing tree in the sidebar"
width="960"
height="638"
loading="lazy"
decoding="async"
/&gt;
&lt;/figure&gt;&lt;/p&gt;
&lt;h2 id="webview-management"&gt;WebView management&lt;/h2&gt;
&lt;p&gt;A browser can&amp;rsquo;t keep unlimited WebViews in memory. Tree uses an LRU cache that caps active WebViews at a configurable maximum (default 4):&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;figcaption&gt;WebKit/WebViewManager.swift&lt;/figcaption&gt;
&lt;div class="highlight" title="WebKit/WebViewManager.swift"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-swift" data-lang="swift"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;evictIfNeeded&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;activeWebViews&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="bp"&gt;count&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;maxActive&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;toEvict&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;accessOrder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="bp"&gt;first&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;where&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;visibleNodes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="bp"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;deallocate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;toEvict&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;toEvict&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;accessOrder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="bp"&gt;first&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;deallocate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;toEvict&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;break&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;The eviction prefers invisible nodes. If both panels are showing WebViews, those are protected: the cache evicts the least recently accessed node that isn&amp;rsquo;t currently on screen. Only when everything is visible does it fall back to strict LRU order.&lt;/p&gt;
&lt;p&gt;Before eviction, the WebView&amp;rsquo;s full browsing state gets persisted to disk.&lt;/p&gt;
&lt;h2 id="state-persistence"&gt;State persistence&lt;/h2&gt;
&lt;p&gt;WebKit exposes &lt;code&gt;interactionState&lt;/code&gt;, an opaque object that captures a WebView&amp;rsquo;s entire browsing state: history stack, scroll position, form data. Tree archives this to Application Support whenever a WebView is evicted, the app backgrounds, or the user closes a panel:&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;figcaption&gt;WebKit/WebViewStateManager.swift&lt;/figcaption&gt;
&lt;div class="highlight" title="WebKit/WebViewStateManager.swift"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-swift" data-lang="swift"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;saveState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;of&lt;/span&gt; &lt;span class="n"&gt;webView&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;WKWebView&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;nodeId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;UUID&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;guard&lt;/span&gt; &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;state&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;webView&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;interactionState&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;data&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="n"&gt;NSKeyedArchiver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;archivedData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;withRootObject&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;requiringSecureCoding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;fileURL&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;stateFileURL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;nodeId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;fileURL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;atomic&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;When a node is revisited and needs a fresh WebView, the manager restores the archived state instead of loading the URL from scratch. The page appears exactly where the user left it: same scroll position, same back/forward history. The user never notices the WebView was recycled.&lt;/p&gt;
&lt;p&gt;A cleanup pass on launch removes orphaned state files for nodes that no longer exist in the database.&lt;/p&gt;
&lt;h2 id="the-sidebar"&gt;The sidebar&lt;/h2&gt;
&lt;p&gt;The sidebar is a recursive SwiftUI tree. Session roots (nodes with no parent) appear at the top level. Each node with children renders as a &lt;code&gt;DisclosureGroup&lt;/code&gt; with nested &lt;code&gt;TreeNodeRow&lt;/code&gt; views:&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;figcaption&gt;Views/SidebarView.swift&lt;/figcaption&gt;
&lt;div class="highlight" title="Views/SidebarView.swift"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-swift" data-lang="swift"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="n"&gt;Query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;#&lt;/span&gt;&lt;span class="n"&gt;Predicate&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;TreeNode&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;$0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parent&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;sessionRoots&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;TreeNode&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;displayedNodes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;TreeNode&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;nodes&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;navigationState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;contextualRoot&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="n"&gt;children&lt;/span&gt; &lt;span class="p"&gt;??&lt;/span&gt; &lt;span class="n"&gt;sessionRoots&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;nodes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="bp"&gt;sorted&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isPinned&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isPinned&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isPinned&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sortOrder&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sortOrder&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;Pinned nodes sort to the top. Below that, manual sort order (set via drag-and-drop reordering) controls the sequence.&lt;/p&gt;
&lt;p&gt;Drag-and-drop also handles reparenting: drag a node onto another node and it becomes a child. The drop handler checks for cycles (you can&amp;rsquo;t drop a parent onto its own descendant) by walking up the target&amp;rsquo;s ancestry chain:&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-swift" data-lang="swift"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;isDescendant&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;of&lt;/span&gt; &lt;span class="n"&gt;ancestorId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;UUID&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Bool&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;current&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;TreeNode&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parent&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;parent&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;current&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;parent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nodeId&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="n"&gt;ancestorId&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;current&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;parent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parent&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;h2 id="focus-mode"&gt;Focus mode&lt;/h2&gt;
&lt;p&gt;A deep tree gets unwieldy. Focus mode lets you temporarily treat any node as the root: the sidebar shows only its children, and a breadcrumb bar at the top shows the path from the true root:&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-swift" data-lang="swift"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;focusBreadcrumbs&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;TreeNode&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;guard&lt;/span&gt; &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;root&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;contextualRoot&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;TreeNode&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;current&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;TreeNode&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;root&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;node&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;current&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="bp"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;at&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;current&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parent&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;Click any breadcrumb to re-focus at that level, or click the home icon to unfocus entirely. The focus state persists across restarts via UserDefaults.&lt;/p&gt;
&lt;h2 id="ad-blocking"&gt;Ad blocking&lt;/h2&gt;
&lt;p&gt;Rather than building content blocking from scratch, Tree loads the full Ghostery browser extension via WebKit&amp;rsquo;s &lt;code&gt;WKWebExtensionController&lt;/code&gt; API. Every WebView gets the extension controller attached to its configuration:&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-swift" data-lang="swift"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;configuration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;webExtensionController&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;WebExtensionManager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shared&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;controller&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;This gives you enterprise-grade tracker and ad blocking with zero maintenance. Ghostery handles its own filter list updates, UI (accessible via a toolbar popover), and persistent storage. The browser just loads the extension at startup and grants permissions.&lt;/p&gt;
&lt;h2 id="keyboard-navigation"&gt;Keyboard navigation&lt;/h2&gt;
&lt;p&gt;Tree navigation is keyboard-driven. The tree structure maps to directional shortcuts:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Shortcut&lt;/th&gt;
&lt;th&gt;Action&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Cmd+Shift+Up&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Go to parent node&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Cmd+Shift+Down&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Go to first child&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Cmd+Shift+Left&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Previous sibling&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Cmd+Shift+Right&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Next sibling&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Cmd+Shift+J&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Quick search overlay&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Cmd+T&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;New session&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Cmd+W&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Close panel&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Cmd+Shift+W&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Prune node and subtree&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The spatial metaphor works because the sidebar &lt;em&gt;is&lt;/em&gt; a tree. Up means parent. Down means child. Left and right move between siblings. Once the mapping clicks, you rarely need the mouse for navigation.&lt;/p&gt;
&lt;p&gt;&lt;figure&gt;
&lt;img
src="https://man-you.ringum.net/posts/tree-browser/tree-sidebar-search-overlay_hu_f061c55a22f7e7f6.webp"
srcset="https://man-you.ringum.net/posts/tree-browser/tree-sidebar-search-overlay_hu_f061c55a22f7e7f6.webp 960w, https://man-you.ringum.net/posts/tree-browser/tree-sidebar-search-overlay_hu_2edff8a9d6417701.webp 3050w"
sizes="(max-width: 960px) 100vw, 960px"
alt="Quick search overlay with ranked results and visit counts"
width="960"
height="638"
loading="lazy"
decoding="async"
/&gt;
&lt;/figure&gt;&lt;/p&gt;
&lt;h2 id="session-restoration"&gt;Session restoration&lt;/h2&gt;
&lt;p&gt;On launch, the app restores the last active primary and secondary nodes, plus the focus state, from UserDefaults:&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-swift" data-lang="swift"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;restoreLastSession&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;from&lt;/span&gt; &lt;span class="n"&gt;nodes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;TreeNode&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;defaults&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;UserDefaults&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;standard&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;primaryIdString&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;defaults&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;forKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;Self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lastPrimaryKey&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;secondaryIdString&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;defaults&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;forKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;Self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lastSecondaryKey&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;contextualRootIdString&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;defaults&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;forKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;Self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;contextualRootKey&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// Match UUIDs to loaded TreeNodes and restore state...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;Combined with WebView state persistence, a restart puts you back exactly where you were: same pages, same scroll positions, same split view layout, same focused subtree.&lt;/p&gt;
&lt;h2 id="what-it-replaces"&gt;What it replaces&lt;/h2&gt;
&lt;p&gt;The thing it replaces isn&amp;rsquo;t another browser. It replaces the workflow of opening a dozen tabs, losing track of which ones are related, and eventually closing them all because you can&amp;rsquo;t remember what you were doing.&lt;/p&gt;
&lt;p&gt;With Tree, the context is the structure. A research session about some API has the docs page as the root, the getting started guide as one branch, the API reference as another, and Stack Overflow answers as leaves. Close the app, come back tomorrow, and the tree is still there, with every page exactly where you left it.&lt;/p&gt;</description></item><item><title>Rate limiting with a single Lua script</title><link>https://man-you.ringum.net/posts/application-kit-ratelimit/</link><pubDate>Tue, 10 Feb 2026 09:49:00 +0200</pubDate><guid>https://man-you.ringum.net/posts/application-kit-ratelimit/</guid><description>&lt;p&gt;We needed rate limiting across all our services: FastAPI, Django, you name it. The usual approach is to grab a library, wire it up per-framework, and accept the slight differences in behavior between them. We went a different way: one Lua script that runs atomically in Redis, with everything else being thin wrappers around it.&lt;/p&gt;
&lt;p&gt;This is how the rate limiting system in &lt;a href="https://github.com/Woosmap/application_kit"&gt;application-kit&lt;/a&gt; works, including per-project overrides, element-based counting, and a monitor mode for gradual rollouts.&lt;/p&gt;
&lt;h2 id="the-problem-with-two-rate-limiters"&gt;The problem with two rate limiters&lt;/h2&gt;
&lt;p&gt;Early on, we had two Lua scripts: one for basic path-based rate limiting and another for per-project limits with override support. They did almost the same thing but diverged just enough to be annoying. Bug fixes had to land in two places. Behavior wasn&amp;rsquo;t always consistent. The classic duplication trap.&lt;/p&gt;
&lt;p&gt;The fix was to collapse both into a single script (&lt;code&gt;PROJECT_RATE_LIMITER_LUA&lt;/code&gt;) with optional parameters that fall back to sensible defaults. No override key? It behaves like a simple limiter. Pass one? It checks per-project overrides atomically.&lt;/p&gt;
&lt;h2 id="one-lua-script-to-rule-them-all"&gt;One Lua script to rule them all&lt;/h2&gt;
&lt;p&gt;The core idea: every rate limit check is a single atomic Redis operation. No round-trips, no race conditions between reading the count and incrementing it.&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;figcaption&gt;The unified Lua script (simplified)&lt;/figcaption&gt;
&lt;div class="highlight" title="The unified Lua script (simplified)"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-lua" data-lang="lua"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;rate_limit_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;KEYS&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;override_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;KEYS&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;default_max_requests&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tonumber&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ARGV&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;default_expiry&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tonumber&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ARGV&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;increment_amount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tonumber&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ARGV&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;-- Check for per-project override&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;max_requests&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;default_max_requests&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;expiry&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;default_expiry&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;if&lt;/span&gt; &lt;span class="n"&gt;override_key&lt;/span&gt; &lt;span class="o"&gt;~=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&amp;#34;&lt;/span&gt; &lt;span class="kr"&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;override&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;redis.call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;HGETALL&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;override_key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;if&lt;/span&gt; &lt;span class="o"&gt;#&lt;/span&gt;&lt;span class="n"&gt;override&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="kr"&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;-- Parse hash fields into max_requests and expiry&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;#&lt;/span&gt;&lt;span class="n"&gt;override&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="kr"&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;if&lt;/span&gt; &lt;span class="n"&gt;override&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;max_requests&amp;#34;&lt;/span&gt; &lt;span class="kr"&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;max_requests&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tonumber&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;override&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;elseif&lt;/span&gt; &lt;span class="n"&gt;override&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;expiry&amp;#34;&lt;/span&gt; &lt;span class="kr"&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;expiry&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tonumber&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;override&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;-- Get current count&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tonumber&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;redis.call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;GET&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rate_limit_key&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;is_over_limit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;if&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="kr"&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;-- First request in this window&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;redis.call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;SET&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rate_limit_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;increment_amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;redis.call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;EXPIRE&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rate_limit_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;expiry&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;increment_amount&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;if&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;max_requests&lt;/span&gt; &lt;span class="kr"&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;is_over_limit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;elseif&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;increment_amount&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;max_requests&lt;/span&gt; &lt;span class="kr"&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;is_over_limit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;else&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;redis.call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;INCRBY&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rate_limit_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;increment_amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;ttl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;redis.call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;TTL&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rate_limit_key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;is_over_limit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ttl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_requests&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;The script takes two keys and three arguments:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;KEYS[1]&lt;/strong&gt;: the counter key (e.g. &lt;code&gt;ratelimit:/api/search:1:42&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;KEYS[2]&lt;/strong&gt;: the override key (empty string means &amp;ldquo;no overrides&amp;rdquo;)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ARGV[1..3]&lt;/strong&gt;: default max requests, expiry in seconds, and increment amount&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Everything after that is Redis doing its thing. One &lt;code&gt;EVALSHA&lt;/code&gt;, one atomic operation, four values back.&lt;/p&gt;
&lt;h2 id="redis-key-layout"&gt;Redis key layout&lt;/h2&gt;
&lt;p&gt;The key format puts the endpoint first and IDs at the end:&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;Counter: ratelimit:{endpoint}:{org_id}:{project_id}
Override: ratelimit_override:{endpoint}:{org_id}:{project_id}&lt;/code&gt;&lt;/pre&gt;
&lt;/figure&gt;
&lt;p&gt;This ordering matters. Endpoint names can contain colons (like &lt;code&gt;api:v1:search&lt;/code&gt;), so we parse keys from the right: the last two segments are always &lt;code&gt;org_id:project_id&lt;/code&gt;. The &lt;code&gt;parse_override_key()&lt;/code&gt; function handles this reliably, stripping the known prefix and extracting integers from the tail.&lt;/p&gt;
&lt;p&gt;Counters auto-expire via Redis TTL. When the window ends, the key vanishes and the next request starts fresh.&lt;/p&gt;
&lt;h2 id="per-project-overrides"&gt;Per-project overrides&lt;/h2&gt;
&lt;p&gt;The override system uses Redis hashes. An override key like &lt;code&gt;ratelimit_override:/api/search:1:42&lt;/code&gt; stores:&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;max_requests = &amp;#34;500&amp;#34;
expiry = &amp;#34;120&amp;#34;&lt;/code&gt;&lt;/pre&gt;
&lt;/figure&gt;
&lt;p&gt;Because the Lua script checks for overrides &lt;em&gt;inside&lt;/em&gt; the same atomic call, there&amp;rsquo;s no window where a request could slip through with stale limits. If an override exists, it replaces the defaults. If it doesn&amp;rsquo;t, the defaults apply. Zero ambiguity.&lt;/p&gt;
&lt;p&gt;Overrides have their own optional TTL (independent of the rate limit window), so you can set a temporary elevated limit that auto-expires:&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;figcaption&gt;Setting a temporary override&lt;/figcaption&gt;
&lt;div class="highlight" title="Setting a temporary override"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;set_rate_limit_override&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;org_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;project_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;endpoint&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;/api/search&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;max_requests&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;expiry&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;120&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;override_ttl&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;86400&lt;/span&gt; &lt;span class="c1"&gt;# Override expires in 24h, limit window is still 120s&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;Management functions (&lt;code&gt;set&lt;/code&gt;, &lt;code&gt;get&lt;/code&gt;, &lt;code&gt;delete&lt;/code&gt;, &lt;code&gt;list&lt;/code&gt;, &lt;code&gt;clear&lt;/code&gt;) are provided but designed for admin/provisioning services. Not every consumer needs them.&lt;/p&gt;
&lt;h2 id="element-based-counting"&gt;Element-based counting&lt;/h2&gt;
&lt;p&gt;Not all requests are equal. A distance matrix call with 10 origins and 5 destinations should count as 50 elements, not 1 request.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;increment_amount&lt;/code&gt; parameter in the Lua script makes this trivial. Instead of &lt;code&gt;INCR&lt;/code&gt;, we use &lt;code&gt;INCRBY&lt;/code&gt;:&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;figcaption&gt;Element-based rate limiting&lt;/figcaption&gt;
&lt;div class="highlight" title="Element-based rate limiting"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Request: 10 origins x 5 destinations = 50 elements&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cols&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;origins&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;destinations&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;apply_element_rate_limit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;max_requests&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;# 1000 elements per minute&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;expiry&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;increment_amount&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;rows&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;cols&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;# This request costs 50&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;redis_client&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;Element counters use a &lt;code&gt;key_suffix&lt;/code&gt; (default: &lt;code&gt;/elements&lt;/code&gt;) to maintain separate counters from regular request counting. Same endpoint, two independent limits:&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;ratelimit:/api/matrix:1:42 → request counter
ratelimit:/api/matrix:/elements:1:42 → element counter&lt;/code&gt;&lt;/pre&gt;
&lt;/figure&gt;
&lt;h2 id="monitor-mode"&gt;Monitor mode&lt;/h2&gt;
&lt;p&gt;Rolling out rate limits on existing APIs is nerve-wracking. You want to know &lt;em&gt;who would be affected&lt;/em&gt; before actually blocking anyone.&lt;/p&gt;
&lt;p&gt;Monitor mode (&lt;code&gt;RATE_LIMIT_MODE=monitor&lt;/code&gt;) runs the full rate limit logic (counting, checking, setting headers) but never returns a 429. Instead, it tags the Datadog root span:&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;figcaption&gt;Monitor mode behavior&lt;/figcaption&gt;
&lt;div class="highlight" title="Monitor mode behavior"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;is_over_limit&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;mode&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;RateLimitMode&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;monitor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;span&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tracer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;current_root_span&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;span&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;span&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set_tag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;ratelimit.over_limit&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;endpoint_path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;Clients still see &lt;code&gt;RateLimit-Remaining: 0&lt;/code&gt; in response headers, so they &lt;em&gt;can&lt;/em&gt; self-throttle if they&amp;rsquo;re polite. But the server won&amp;rsquo;t enforce it.&lt;/p&gt;
&lt;p&gt;Three modes, set via the Bender manifest:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Mode&lt;/th&gt;
&lt;th&gt;Counts&lt;/th&gt;
&lt;th&gt;Headers&lt;/th&gt;
&lt;th&gt;Blocks&lt;/th&gt;
&lt;th&gt;Datadog tag&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;on&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes (429)&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;off&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;monitor&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The setting is read dynamically on every request. No restart needed to switch modes.&lt;/p&gt;
&lt;h2 id="framework-wrappers"&gt;Framework wrappers&lt;/h2&gt;
&lt;h3 id="fastapi-three-entry-points"&gt;FastAPI: three entry points&lt;/h3&gt;
&lt;p&gt;FastAPI gets the most complete support with three ways to rate limit:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1. ProjectRateLimiter&lt;/strong&gt;: the recommended default. Uses dependency injection, extracts project identity from the authenticated request, supports overrides and monitor mode:&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;figcaption&gt;FastAPI ProjectRateLimiter&lt;/figcaption&gt;
&lt;div class="highlight" title="FastAPI ProjectRateLimiter"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nd"&gt;@router.get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;/search&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;dependencies&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Depends&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ProjectRateLimiter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_requests&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;expiry&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;))],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;...&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;It follows the template method pattern: &lt;code&gt;make_key()&lt;/code&gt; and &lt;code&gt;make_override_key()&lt;/code&gt; can be overridden without touching the rate limit logic itself:&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;figcaption&gt;Custom rate limiter via subclassing&lt;/figcaption&gt;
&lt;div class="highlight" title="Custom rate limiter via subclassing"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;GeoRateLimiter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ProjectRateLimiter&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;make_key&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;region&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;X-Region&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;default&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;base&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;super&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;make_key&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;region&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;base&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;&lt;strong&gt;2. PathRateLimiter&lt;/strong&gt;: simpler, no project isolation. Useful for public or webhook endpoints where there&amp;rsquo;s no authenticated project:&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;figcaption&gt;FastAPI PathRateLimiter&lt;/figcaption&gt;
&lt;div class="highlight" title="FastAPI PathRateLimiter"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nd"&gt;@router.post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;/webhook&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;dependencies&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Depends&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PathRateLimiter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_requests&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;expiry&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;))],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;webhook&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;...&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;&lt;strong&gt;3. Programmatic API&lt;/strong&gt;: &lt;code&gt;apply_rate_limit()&lt;/code&gt; and &lt;code&gt;apply_element_rate_limit()&lt;/code&gt; for when limits depend on request content:&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;figcaption&gt;Runtime-determined limits&lt;/figcaption&gt;
&lt;div class="highlight" title="Runtime-determined limits"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;process_batch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;batch_size&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;items&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;apply_element_rate_limit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;max_requests&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;expiry&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;increment_amount&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;batch_size&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;redis_client&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;h3 id="django-decorator-based"&gt;Django: decorator-based&lt;/h3&gt;
&lt;p&gt;Django uses a &lt;code&gt;@rate_limit&lt;/code&gt; decorator that auto-detects sync vs. async views:&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;figcaption&gt;Django rate limiting&lt;/figcaption&gt;
&lt;div class="highlight" title="Django rate limiting"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nd"&gt;@authenticate_key&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nd"&gt;@rate_limit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_requests&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;expiry&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;my_view&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;JsonResponse&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;status&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;ok&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;The endpoint name is auto-detected from the URL pattern (e.g. &lt;code&gt;/api/v1/datasets/&amp;lt;int:dataset_id&amp;gt;/search&lt;/code&gt;). Django middleware catches &lt;code&gt;RateLimitExceeded&lt;/code&gt; exceptions and returns a 429 with the proper rate limit headers.&lt;/p&gt;
&lt;p&gt;Both frameworks share the same Lua script, the same key generation functions, and the same result handling logic. The wrappers are genuinely thin.&lt;/p&gt;
&lt;h2 id="the-result-object"&gt;The result object&lt;/h2&gt;
&lt;p&gt;Every rate limit check produces a &lt;code&gt;RateLimitResult&lt;/code&gt;:&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;figcaption&gt;RateLimitResult&lt;/figcaption&gt;
&lt;div class="highlight" title="RateLimitResult"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nd"&gt;@dataclass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;frozen&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;RateLimitResult&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;is_over_limit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;request_count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;ttl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;max_requests&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nd"&gt;@property&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;remaining&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;max_requests&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request_count&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;to_headers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;RateLimit-Limit&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;max_requests&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;RateLimit-Remaining&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;remaining&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;RateLimit-Reset&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ttl&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;Frozen dataclass, immutable, with a convenience method for standard rate limit headers. Every response (except when mode is &lt;code&gt;off&lt;/code&gt;) gets these three headers so clients can implement their own backoff.&lt;/p&gt;
&lt;h2 id="what-made-this-work"&gt;What made this work&lt;/h2&gt;
&lt;p&gt;A few design decisions that paid off:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Atomic override resolution.&lt;/strong&gt; The Lua script checks overrides &lt;em&gt;inside&lt;/em&gt; the same call that does the counting. No separate Redis roundtrip means no race condition between &amp;ldquo;check the override&amp;rdquo; and &amp;ldquo;apply the limit.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Optional parameters with backwards-compatible defaults.&lt;/strong&gt; &lt;code&gt;override_key=&amp;quot;&amp;quot;&lt;/code&gt; and &lt;code&gt;increment_amount=1&lt;/code&gt; mean the same script handles simple path limiting, per-project limiting, and element counting. No script duplication.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Dynamic configuration.&lt;/strong&gt; &lt;code&gt;get_rate_limit_mode()&lt;/code&gt; calls into Bender on every request. No cached settings, no restarts. Switch from &lt;code&gt;monitor&lt;/code&gt; to &lt;code&gt;on&lt;/code&gt; when you&amp;rsquo;re confident.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Separate counters via key suffix.&lt;/strong&gt; Element counting doesn&amp;rsquo;t interfere with request counting. Same endpoint, independent limits, same Lua script.&lt;/p&gt;
&lt;p&gt;The whole thing is tested with &lt;code&gt;fakeredis[lua]&lt;/code&gt;, which actually executes the Lua script, so the tests are as close to production behavior as you can get without a real Redis.&lt;/p&gt;</description></item><item><title>Building a GPU map renderer from scratch</title><link>https://man-you.ringum.net/posts/gpu-map-renderer/</link><pubDate>Tue, 10 Feb 2026 09:00:00 +0200</pubDate><guid>https://man-you.ringum.net/posts/gpu-map-renderer/</guid><description>&lt;p&gt;Building a vector map renderer in Rust with wgpu. From &amp;ldquo;everything renders as points&amp;rdquo; to a full style-driven renderer running on desktop, iOS, and the browser.&lt;/p&gt;
&lt;h2 id="why"&gt;Why&lt;/h2&gt;
&lt;p&gt;We built our maps stack on top of Mapbox GL: the JS SDK, then native SDKs on the C++ core.&lt;/p&gt;
&lt;p&gt;For static maps we wrapped the C++ SDK in a Python library (&lt;a href="https://man-you.ringum.net/backroom/static_maps/"&gt;maparazzo&lt;/a&gt;). It worked but the C++ renderer leaked memory. OpenGL contexts kept state around after objects were destroyed. We tried pooling &lt;code&gt;Map&lt;/code&gt; instances, reusing GL contexts. It helped but never fully solved it.&lt;/p&gt;
&lt;p&gt;I wanted to understand every layer of the stack. What if we built a renderer from scratch, something we fully own, no inherited complexity from a massive C++ codebase?&lt;/p&gt;
&lt;p&gt;Rust + wgpu seemed right. wgpu abstracts over Metal/Vulkan/DX12/WebGPU so you get native + browser from one codebase. Rust gives you memory safety without a GC. No more mystery leaks.&lt;/p&gt;
&lt;h2 id="first-pixels"&gt;First pixels&lt;/h2&gt;
&lt;p&gt;Started from the &lt;a href="https://github.com/nickmass/wgpu-triangle"&gt;wgpu-triangle&lt;/a&gt; example: a bare-bones triangle on screen. Built the stem by hand: tile fetching, MVT decoding, basic projection. From there I heavily leveraged Claude Code to help push through the harder parts and get it to where it is now.&lt;/p&gt;
&lt;p&gt;Everything in one file. &lt;code&gt;main.rs&lt;/code&gt; at 1,500 lines: MVT decoder, protobuf parser, renderer, tile fetching, all of it.&lt;/p&gt;
&lt;p&gt;The MVT decoder is hand-rolled. No prost, no generated code. Protobuf is simple enough at the wire level: varints, length-delimited fields. The tile format uses a local coordinate grid (4096x4096 per tile) with delta-encoded geometry commands.&lt;/p&gt;
&lt;p&gt;First render: every feature drawn as points. Polygons, lines, everything, just dots on screen. But dots in the right places.&lt;/p&gt;
&lt;h2 id="making-it-a-real-map"&gt;Making it a real map&lt;/h2&gt;
&lt;p&gt;Extracted modules one by one. &lt;code&gt;mvt.rs&lt;/code&gt; for tile decoding, &lt;code&gt;renderer.rs&lt;/code&gt; for GPU work, &lt;code&gt;tile_loader.rs&lt;/code&gt; for fetching, &lt;code&gt;tile_coords.rs&lt;/code&gt; for slippy map math.&lt;/p&gt;
&lt;p&gt;Added &lt;code&gt;earcutr&lt;/code&gt; for polygon triangulation: it converts arbitrary polygons with holes into triangles the GPU can draw. First tessellation attempt produced spikes everywhere. The MVT spec requires rings to be closed but some tiles had implicit closure. Two-pass decode: first pass collects keys/values/feature ranges, second pass builds features with proper ring closing.&lt;/p&gt;
&lt;p&gt;Background tile loading with worker threads and &lt;code&gt;mpsc&lt;/code&gt; channels. Scroll zoom, mouse pan. Viewport buffer zone to pre-load surrounding tiles so panning feels instant.&lt;/p&gt;
&lt;p&gt;Tessellation cache keyed by &lt;code&gt;(TileCoord, layer_index, feature_index)&lt;/code&gt;: earcut is expensive, no need to redo it when the camera moves.&lt;/p&gt;
&lt;h2 id="moving-projection-to-the-gpu"&gt;Moving projection to the GPU&lt;/h2&gt;
&lt;p&gt;The big architectural decision. Originally the CPU converted every vertex from lon/lat to screen pixels. Camera move = rebuild all vertices = slow.&lt;/p&gt;
&lt;p&gt;Flipped it: vertices store world lon/lat, the GPU shader does Web Mercator projection. Camera moves only update a 64-byte uniform buffer. Vertex data stays untouched.&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;center_mercator, scale, rotation, viewport_scale, aspect, viewport_size&lt;/code&gt;&lt;/pre&gt;
&lt;/figure&gt;
&lt;p&gt;This means &lt;code&gt;mark_camera_dirty()&lt;/code&gt; for pan/zoom (cheap uniform upload), &lt;code&gt;mark_geometry_dirty()&lt;/code&gt; only when tiles arrive or leave. Night and day difference for interactivity.&lt;/p&gt;
&lt;h2 id="style-driven-rendering"&gt;Style-driven rendering&lt;/h2&gt;
&lt;p&gt;A map renderer without style support is just a polygon viewer. Built an expression engine and style evaluator.&lt;/p&gt;
&lt;p&gt;The expression engine handles the Mapbox style spec: &lt;code&gt;get&lt;/code&gt;, &lt;code&gt;has&lt;/code&gt;, &lt;code&gt;match&lt;/code&gt;, &lt;code&gt;case&lt;/code&gt;, &lt;code&gt;interpolate&lt;/code&gt;, &lt;code&gt;step&lt;/code&gt;, &lt;code&gt;let/var&lt;/code&gt;, &lt;code&gt;coalesce&lt;/code&gt;, arithmetic, string ops, comparisons. &lt;code&gt;let/var&lt;/code&gt; was critical: Woosmap styles use variable bindings for i18n name resolution.&lt;/p&gt;
&lt;p&gt;Filter compilation: both legacy &lt;code&gt;[&amp;quot;==&amp;quot;, &amp;quot;key&amp;quot;, val]&lt;/code&gt; and modern &lt;code&gt;[&amp;quot;==&amp;quot;, [&amp;quot;get&amp;quot;,&amp;quot;key&amp;quot;], val]&lt;/code&gt; syntax.&lt;/p&gt;
&lt;p&gt;The key insight for z-ordering: iterate style layers, not MVT layers. The style defines the draw order. For each style layer, find matching features across all tiles.&lt;/p&gt;
&lt;p&gt;Background color comes straight from the style: just set the wgpu clear color.&lt;/p&gt;
&lt;p&gt;Paris at different zoom levels, same style, same renderer, z4 to z16:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;figure&gt;
&lt;img
src="https://man-you.ringum.net/posts/gpu-map-renderer/paris_z4_hu_c32f6d7c3bdeea63.webp"
alt="z4"
width="512"
height="512"
loading="lazy"
decoding="async"
/&gt;
&lt;/figure&gt;&lt;/td&gt;
&lt;td&gt;&lt;figure&gt;
&lt;img
src="https://man-you.ringum.net/posts/gpu-map-renderer/paris_z8_hu_cf8793219bec02c5.webp"
alt="z8"
width="512"
height="512"
loading="lazy"
decoding="async"
/&gt;
&lt;/figure&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;z4, country level&lt;/td&gt;
&lt;td&gt;z8, region level&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;figure&gt;
&lt;img
src="https://man-you.ringum.net/posts/gpu-map-renderer/paris_z12_hu_cae9a6fc5531d16f.webp"
alt="z12"
width="512"
height="512"
loading="lazy"
decoding="async"
/&gt;
&lt;/figure&gt;&lt;/td&gt;
&lt;td&gt;&lt;figure&gt;
&lt;img
src="https://man-you.ringum.net/posts/gpu-map-renderer/paris_z16_hu_d8d37d73d56e90d8.webp"
alt="z16"
width="512"
height="512"
loading="lazy"
decoding="async"
/&gt;
&lt;/figure&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;z12, city level&lt;/td&gt;
&lt;td&gt;z16, street level&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id="lines"&gt;Lines&lt;/h2&gt;
&lt;p&gt;Lines in a GPU renderer are not trivial. A &amp;ldquo;line&amp;rdquo; on screen is actually a quad (two triangles) for each segment, with normals perpendicular to the direction. Line joins at vertices need special treatment.&lt;/p&gt;
&lt;p&gt;Implemented miter, bevel, and round joins. Miter joins can spike to infinity at sharp angles, so clamp with a miter limit and fall back to bevel. Round joins emit a fan of triangles.&lt;/p&gt;
&lt;p&gt;Line normals computed in Mercator space, not screen space, because of the GPU projection design. The shader transforms them correctly.&lt;/p&gt;
&lt;h2 id="icons-and-sprites"&gt;Icons and sprites&lt;/h2&gt;
&lt;p&gt;Sprite atlas: load &lt;code&gt;{url}.json&lt;/code&gt; (metadata) + &lt;code&gt;{url}.png&lt;/code&gt; (texture). Each icon is a region in the atlas with UV coordinates.&lt;/p&gt;
&lt;p&gt;SDF (Signed Distance Field) rendering in the fragment shader. One shader handles both SDF sprites and regular RGBA sprites: the &lt;code&gt;is_sdf&lt;/code&gt; field on each vertex switches behavior. SDF gives you clean scaling and halos at any size from a single texture.&lt;/p&gt;
&lt;p&gt;Icon quads: 4 vertices + 6 indices per point. Screen-aligned: the anchor position projects through the map transform but pixel offsets stay fixed.&lt;/p&gt;
&lt;p&gt;@2x sprite support for Retina: load the high-res atlas, halve the pixel sizes.&lt;/p&gt;
&lt;h2 id="text"&gt;Text&lt;/h2&gt;
&lt;p&gt;Text was the biggest single feature. PBF glyph format (same as Mapbox), shelf-packed texture atlas, SDF rendering reusing the icon shader.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;GlyphAtlas&lt;/code&gt; dynamically grows (1024x1024 initial, doubles when full). SDF distance stored in alpha channel. The glyph shader is literally the icon shader: &lt;code&gt;is_sdf = 2.0 + halo_buff&lt;/code&gt; triggers PBF glyph mode with the correct SDF thresholds.&lt;/p&gt;
&lt;p&gt;Text shaping is 1:1 codepoint-to-glyph. No GSUB/GPOS, no complex script support yet. Works for Latin, CJK, Cyrillic. Arabic and Thai would need a real shaper like rustybuzz.&lt;/p&gt;
&lt;p&gt;Word wrapping, text-anchor (9 positions), text-offset, text-variable-anchor with collision retry, text-radial-offset, text-justify.&lt;/p&gt;
&lt;h3 id="collision-detection"&gt;Collision detection&lt;/h3&gt;
&lt;p&gt;Without collision detection, labels pile on top of each other. Built a grid-based system.&lt;/p&gt;
&lt;p&gt;Labels grouped by feature: icon + text for the same feature are placed together (all-or-nothing). Groups sorted by &lt;code&gt;(layer_index, symbol-sort-key)&lt;/code&gt;. Grid cells are checked, if occupied the label gets rejected and fades out.&lt;/p&gt;
&lt;p&gt;Variable anchor retry: if a label&amp;rsquo;s primary anchor collides, try alternatives. Pre-compute screen positions for each anchor variant, test them in order. Phase 1: collect decisions. Phase 2: apply vertex shifts. Two phases to avoid borrow conflicts.&lt;/p&gt;
&lt;p&gt;A tour of European cities, all rendered with the same pipeline:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;figure&gt;
&lt;img
src="https://man-you.ringum.net/posts/gpu-map-renderer/paris_hu_6211f3a92783251d.webp"
alt="Paris"
width="512"
height="512"
loading="lazy"
decoding="async"
/&gt;
&lt;/figure&gt;&lt;/td&gt;
&lt;td&gt;&lt;figure&gt;
&lt;img
src="https://man-you.ringum.net/posts/gpu-map-renderer/london_hu_25af5b8c4b6a10bc.webp"
alt="London"
width="512"
height="512"
loading="lazy"
decoding="async"
/&gt;
&lt;/figure&gt;&lt;/td&gt;
&lt;td&gt;&lt;figure&gt;
&lt;img
src="https://man-you.ringum.net/posts/gpu-map-renderer/barcelona_hu_b9c4ed6118df85ff.webp"
alt="Barcelona"
width="512"
height="512"
loading="lazy"
decoding="async"
/&gt;
&lt;/figure&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Paris&lt;/td&gt;
&lt;td&gt;London&lt;/td&gt;
&lt;td&gt;Barcelona&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;figure&gt;
&lt;img
src="https://man-you.ringum.net/posts/gpu-map-renderer/amsterdam_hu_7fe112a1b0114b9.webp"
alt="Amsterdam"
width="512"
height="512"
loading="lazy"
decoding="async"
/&gt;
&lt;/figure&gt;&lt;/td&gt;
&lt;td&gt;&lt;figure&gt;
&lt;img
src="https://man-you.ringum.net/posts/gpu-map-renderer/berlin_hu_639b66bd8a5549ec.webp"
alt="Berlin"
width="512"
height="512"
loading="lazy"
decoding="async"
/&gt;
&lt;/figure&gt;&lt;/td&gt;
&lt;td&gt;&lt;figure&gt;
&lt;img
src="https://man-you.ringum.net/posts/gpu-map-renderer/rome_hu_d473484829cc4406.webp"
alt="Rome"
width="512"
height="512"
loading="lazy"
decoding="async"
/&gt;
&lt;/figure&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Amsterdam&lt;/td&gt;
&lt;td&gt;Berlin&lt;/td&gt;
&lt;td&gt;Rome&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;figure&gt;
&lt;img
src="https://man-you.ringum.net/posts/gpu-map-renderer/vienna_hu_676fffd2db7f694.webp"
alt="Vienna"
width="512"
height="512"
loading="lazy"
decoding="async"
/&gt;
&lt;/figure&gt;&lt;/td&gt;
&lt;td&gt;&lt;figure&gt;
&lt;img
src="https://man-you.ringum.net/posts/gpu-map-renderer/prague_hu_5ed203892b80f3b3.webp"
alt="Prague"
width="512"
height="512"
loading="lazy"
decoding="async"
/&gt;
&lt;/figure&gt;&lt;/td&gt;
&lt;td&gt;&lt;figure&gt;
&lt;img
src="https://man-you.ringum.net/posts/gpu-map-renderer/budapest_hu_e7f69141d5834c55.webp"
alt="Budapest"
width="512"
height="512"
loading="lazy"
decoding="async"
/&gt;
&lt;/figure&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Vienna&lt;/td&gt;
&lt;td&gt;Prague&lt;/td&gt;
&lt;td&gt;Budapest&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;figure&gt;
&lt;img
src="https://man-you.ringum.net/posts/gpu-map-renderer/stockholm_hu_3f396d9181ac3e22.webp"
alt="Stockholm"
width="512"
height="512"
loading="lazy"
decoding="async"
/&gt;
&lt;/figure&gt;&lt;/td&gt;
&lt;td&gt;&lt;figure&gt;
&lt;img
src="https://man-you.ringum.net/posts/gpu-map-renderer/istanbul_hu_86bbb62670f9ce8d.webp"
alt="Istanbul"
width="512"
height="512"
loading="lazy"
decoding="async"
/&gt;
&lt;/figure&gt;&lt;/td&gt;
&lt;td&gt;&lt;figure&gt;
&lt;img
src="https://man-you.ringum.net/posts/gpu-map-renderer/athens_hu_6cfd42c0fad118c3.webp"
alt="Athens"
width="512"
height="512"
loading="lazy"
decoding="async"
/&gt;
&lt;/figure&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stockholm&lt;/td&gt;
&lt;td&gt;Istanbul&lt;/td&gt;
&lt;td&gt;Athens&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;figure&gt;
&lt;img
src="https://man-you.ringum.net/posts/gpu-map-renderer/lisbon_hu_7c15df78ac605bbb.webp"
alt="Lisbon"
width="512"
height="512"
loading="lazy"
decoding="async"
/&gt;
&lt;/figure&gt;&lt;/td&gt;
&lt;td&gt;&lt;figure&gt;
&lt;img
src="https://man-you.ringum.net/posts/gpu-map-renderer/copenhagen_hu_370a9ccc7139df88.webp"
alt="Copenhagen"
width="512"
height="512"
loading="lazy"
decoding="async"
/&gt;
&lt;/figure&gt;&lt;/td&gt;
&lt;td&gt;&lt;figure&gt;
&lt;img
src="https://man-you.ringum.net/posts/gpu-map-renderer/helsinki_hu_f9e1048d2773f1f.webp"
alt="Helsinki"
width="512"
height="512"
loading="lazy"
decoding="async"
/&gt;
&lt;/figure&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lisbon&lt;/td&gt;
&lt;td&gt;Copenhagen&lt;/td&gt;
&lt;td&gt;Helsinki&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;figure&gt;
&lt;img
src="https://man-you.ringum.net/posts/gpu-map-renderer/oslo_hu_28096293f891d16.webp"
alt="Oslo"
width="512"
height="512"
loading="lazy"
decoding="async"
/&gt;
&lt;/figure&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Oslo&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id="performance"&gt;Performance&lt;/h2&gt;
&lt;p&gt;Event-driven loop with &lt;code&gt;ControlFlow::Wait&lt;/code&gt;. No busy polling. &lt;code&gt;EventLoopProxy&lt;/code&gt; waker: worker threads signal the event loop when tiles arrive.&lt;/p&gt;
&lt;p&gt;Extract debounce: during active zoom/pan (100ms window), skip full geometry extraction, just update camera uniforms. Once input stops, run the deferred extract. New tile arrivals always trigger extract immediately: you want to see them.&lt;/p&gt;
&lt;p&gt;Zoom style patches: instead of re-extracting everything when zoom changes (colors and sizes are zoom-dependent), patch the extracted vertex data in-place. Rewrites only the color/opacity/width fields.&lt;/p&gt;
&lt;p&gt;Time-sliced extraction: spread heavy work across multiple frames so the renderer doesn&amp;rsquo;t stall.&lt;/p&gt;
&lt;p&gt;Dependencies optimized in debug builds: &lt;code&gt;[profile.dev.package.&amp;quot;*&amp;quot;] opt-level = 2&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="multi-platform"&gt;Multi-platform&lt;/h2&gt;
&lt;h3 id="macos--ios-with-uniffi"&gt;macOS / iOS with UniFFI&lt;/h3&gt;
&lt;p&gt;UniFFI generates Swift bindings from Rust. &lt;code&gt;MapEngine&lt;/code&gt; wraps wgpu in a &lt;code&gt;Mutex&lt;/code&gt;, exposes methods like &lt;code&gt;draw_frame()&lt;/code&gt;, &lt;code&gt;set_center()&lt;/code&gt;, &lt;code&gt;set_zoom()&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The Metal surface is created from a raw &lt;code&gt;CAMetalLayer&lt;/code&gt; pointer. MSAA disabled in the FFI path because MTKView&amp;rsquo;s MSAA resolve triggers a size mismatch with wgpu. sRGB correction handled by a uniform flag: fragment shaders apply sRGB-to-linear when rendering to MTKView&amp;rsquo;s sRGB surface.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;HttpClientDelegate&lt;/code&gt; callback interface: Swift injects a URLSession-backed implementation so network requests go through the platform&amp;rsquo;s native stack.&lt;/p&gt;
&lt;p&gt;xcframework build: &lt;code&gt;aarch64-apple-darwin&lt;/code&gt;, &lt;code&gt;x86_64-apple-darwin&lt;/code&gt;, &lt;code&gt;aarch64-apple-ios&lt;/code&gt;, &lt;code&gt;aarch64-apple-ios-sim&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id="browser-with-wasm"&gt;Browser with WASM&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;wasm32-unknown-unknown&lt;/code&gt; target, &lt;code&gt;wasm-bindgen&lt;/code&gt; for the JS interface. Tile loading uses browser &lt;code&gt;fetch()&lt;/code&gt; via &lt;code&gt;web_sys&lt;/code&gt;. No threads in WASM so tile extraction is sequential (10-30 tiles is fine without parallelism).&lt;/p&gt;
&lt;p&gt;Style/sprite loading is async in the WASM path (browser fetch) vs sync on native (ureq). The renderer has platform-agnostic setters: &lt;code&gt;apply_style_data()&lt;/code&gt;, &lt;code&gt;set_sprite_atlas()&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;web-time&lt;/code&gt; crate replaces &lt;code&gt;std::time::Instant&lt;/code&gt; everywhere: re-exports std on native, &lt;code&gt;performance.now()&lt;/code&gt; on WASM.&lt;/p&gt;
&lt;h3 id="globe"&gt;Globe&lt;/h3&gt;
&lt;p&gt;Optional 3D sphere projection at low zoom (toggle with G key). The shader branches on a &lt;code&gt;globe_mix&lt;/code&gt; uniform: above 0.5 it projects lon/lat onto a unit sphere, rotated by center latitude and map rotation.&lt;/p&gt;
&lt;p&gt;Back-face discard in the fragment shader: &lt;code&gt;globe_rz &amp;lt; 0.0&lt;/code&gt; hides the back of the sphere. No depth buffer needed.&lt;/p&gt;
&lt;p&gt;Polygon subdivision for the globe: edges longer than 5 degrees get split recursively. Conforming subdivision: all edges of a triangle get split (not just the longest) so adjacent triangles agree on shared edge vertices. No T-junction cracks.&lt;/p&gt;
&lt;p&gt;&lt;figure&gt;
&lt;img
src="https://man-you.ringum.net/posts/gpu-map-renderer/globe_france_hu_a8a2967961070efb.webp"
alt="Globe projection centered on France"
width="800"
height="800"
loading="lazy"
decoding="async"
/&gt;
&lt;/figure&gt;&lt;/p&gt;
&lt;h2 id="google-maps-styler"&gt;Google Maps Styler&lt;/h2&gt;
&lt;p&gt;A declarative styling engine that applies Google Maps-style JSON rules to modify layer colors. HSL transforms: hue, saturation, lightness, invert, gamma. Delta-based: rules shift colors rather than setting absolute values.&lt;/p&gt;
&lt;p&gt;Hierarchical feature type matching with a tree structure. &lt;code&gt;&amp;quot;all&amp;quot;&lt;/code&gt; walks the entire tree, &lt;code&gt;&amp;quot;road.highway&amp;quot;&lt;/code&gt; matches just highway layers.&lt;/p&gt;
&lt;p&gt;POI expansion: the styler takes a single POI layer with class metadata and expands enabled classes into individual layers, each with its own filter and icon.&lt;/p&gt;
&lt;h2 id="where-it-stands"&gt;Where it stands&lt;/h2&gt;
&lt;p&gt;This is not done. It&amp;rsquo;s not a replacement for Mapbox GL Native, not even close.&lt;/p&gt;
&lt;p&gt;233 tests. The basics work: fill, line, background, symbol layers with icons and text. Expressions, collision detection, variable anchor, text wrapping, text justification. Runs on macOS, iOS, and the browser. Globe projection at low zoom.&lt;/p&gt;
&lt;p&gt;What&amp;rsquo;s missing or broken:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Text shaping&lt;/strong&gt; is naive 1:1 codepoint-to-glyph. Arabic, Thai, Burmese, anything that needs ligatures or reordering doesn&amp;rsquo;t work. Need rustybuzz or equivalent.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Line rendering&lt;/strong&gt; has no dashes, no gradients, no line-cap styles beyond basic round.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Symbol placement along lines&lt;/strong&gt; is basic. No label curving along the road, no smooth rotation interpolation.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Collision detection&lt;/strong&gt; is coarse. The grid works but it&amp;rsquo;s not as smart as Mapbox&amp;rsquo;s: no cross-tile collision, no viewport-edge handling for partially visible labels.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Raster layers&lt;/strong&gt; not supported at all. No hillshade, no satellite imagery compositing.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Performance&lt;/strong&gt; is acceptable for interactive use but extraction is still heavier than it should be. Mapbox GL Native has had years of profiling and optimization. We haven&amp;rsquo;t.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Fill extrusion&lt;/strong&gt; (3D buildings) not implemented.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Rotation&lt;/strong&gt; mostly works but some label placement breaks at non-zero bearing.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Expression coverage&lt;/strong&gt; is incomplete, missing &lt;code&gt;format&lt;/code&gt;, &lt;code&gt;image&lt;/code&gt;, &lt;code&gt;number-format&lt;/code&gt;, &lt;code&gt;to-color&lt;/code&gt; and several type conversion operators.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;About 15,000 lines of Rust. No C++ dependency. No OpenGL. The foundation is there, the details are not.&lt;/p&gt;</description></item><item><title>Migrating Python containers to Wolfi and uv</title><link>https://man-you.ringum.net/posts/wolfi-uv/</link><pubDate>Tue, 10 Feb 2026 09:00:00 +0200</pubDate><guid>https://man-you.ringum.net/posts/wolfi-uv/</guid><description>&lt;p&gt;Our Python services ran on &lt;code&gt;ubuntu:24.04&lt;/code&gt; with pip-installed dependencies. It worked, but the images carried hundreds of packages we never used, Trivy scans were noisy with OS-level CVEs, and builds were slower than they needed to be. Over a couple of months we migrated to Chainguard&amp;rsquo;s Wolfi base image and uv for dependency management. This is how it went for the maps service, the one that renders static map tiles with a C++/Python hybrid stack.&lt;/p&gt;
&lt;h2 id="cleaning-up-dependency-management-with-uv"&gt;Cleaning up dependency management with uv&lt;/h2&gt;
&lt;p&gt;Before touching the base image, we sorted out the dependency mess. The maps service had drifted. &lt;code&gt;pyproject.toml&lt;/code&gt; declared one set of versions, &lt;code&gt;uv.lock&lt;/code&gt; referenced another, and a private package (&lt;code&gt;maparazzo&lt;/code&gt;) was pinned to a version that didn&amp;rsquo;t exist on the registry anymore.&lt;/p&gt;
&lt;p&gt;The first PR synced the lock file with reality and bumped &lt;code&gt;maparazzo&lt;/code&gt; to a published version. The second one removed version constraints from &lt;code&gt;pyproject.toml&lt;/code&gt; entirely, keeping only the lock file as the source of truth:&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;figcaption&gt;pyproject.toml (before)&lt;/figcaption&gt;
&lt;div class="highlight" title="pyproject.toml (before)"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-toml" data-lang="toml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;dependencies&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;application_kit[fastapi]&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;fastapi&amp;gt;=0.115.0&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;maparazzo==0.4.0&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;aiosqlite&amp;gt;=0.20.0&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;valkey-glide==2.2.5&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;sniffio&amp;gt;=1.3.1&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c"&gt;# Dev tools&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;types-redis&amp;gt;=4.6.0&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;coverage[toml]&amp;gt;=7.6.0&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;pytest&amp;gt;=8.3.0&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;pytest-asyncio&amp;gt;=0.24.0&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;pytest-cov&amp;gt;=6.0.0&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;respx&amp;gt;=0.22.0&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;ruff&amp;gt;=0.8.0&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;mypy&amp;gt;=1.14.0&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;figure class="code-block-figure"&gt;
&lt;figcaption&gt;pyproject.toml (after)&lt;/figcaption&gt;
&lt;div class="highlight" title="pyproject.toml (after)"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-toml" data-lang="toml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;dependencies&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;application_kit[fastapi]&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;maparazzo==2.0.0&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;aiosqlite&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;valkey-glide==2.2.5&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;sniffio&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;dependency-groups&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;dev&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;types-redis&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;coverage[toml]&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;pytest&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;pytest-asyncio&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;pytest-cov&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;respx&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;ruff&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;mypy&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;Two changes happened here. First, lower bounds like &lt;code&gt;&amp;gt;=0.115.0&lt;/code&gt; were dropped: the lock file already pins exact versions, so the constraints in &lt;code&gt;pyproject.toml&lt;/code&gt; were just noise that could silently go stale. Second, dev tools moved to a proper &lt;code&gt;[dependency-groups]&lt;/code&gt; section. This matters for the multi-stage build that comes next: &lt;code&gt;uv sync --no-dev&lt;/code&gt; now skips pytest, ruff, mypy, and the rest. Previously they all shipped in the production image.&lt;/p&gt;
&lt;p&gt;Running &lt;code&gt;uv lock --upgrade&lt;/code&gt; after removing the constraints updated 23 packages in under 3 seconds. That&amp;rsquo;s the part of uv that makes the biggest day-to-day difference: resolving a 60-package graph is nearly instant.&lt;/p&gt;
&lt;h2 id="the-ubuntu-dockerfile"&gt;The Ubuntu Dockerfile&lt;/h2&gt;
&lt;p&gt;Here&amp;rsquo;s what we had before:&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;figcaption&gt;Dockerfile (Ubuntu-based)&lt;/figcaption&gt;
&lt;div class="highlight" title="Dockerfile (Ubuntu-based)"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-Dockerfile" data-lang="Dockerfile"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;ubuntu:24.04&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;base&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;ENV&lt;/span&gt; &lt;span class="nv"&gt;UV_COMPILE_BYTECODE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="nv"&gt;UV_LINK_MODE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;copy
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;ENV&lt;/span&gt; &lt;span class="nv"&gt;UV_PYTHON_INSTALL_DIR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/python&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;ENV&lt;/span&gt; &lt;span class="nv"&gt;UV_PYTHON_PREFERENCE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;only-managed&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;COPY&lt;/span&gt; --from&lt;span class="o"&gt;=&lt;/span&gt;ghcr.io/astral-sh/uv:latest /uv /uvx /bin/&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;RUN&lt;/span&gt; uv python install 3.12&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;/usr/src/app&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;ENV&lt;/span&gt; &lt;span class="nv"&gt;UV_PROJECT_ENVIRONMENT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/usr/src/venv&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;RUN&lt;/span&gt; --mount&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;cache,target&lt;span class="o"&gt;=&lt;/span&gt;/root/.cache/uv &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --mount&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;bind,source&lt;span class="o"&gt;=&lt;/span&gt;uv.lock,target&lt;span class="o"&gt;=&lt;/span&gt;uv.lock &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --mount&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;bind,source&lt;span class="o"&gt;=&lt;/span&gt;pyproject.toml,target&lt;span class="o"&gt;=&lt;/span&gt;pyproject.toml &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; uv sync --frozen --no-install-project --no-dev&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;ADD&lt;/span&gt; . /usr/src/app&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;RUN&lt;/span&gt; --mount&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;cache,target&lt;span class="o"&gt;=&lt;/span&gt;/root/.cache/uv &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; uv sync --frozen --no-dev&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;ubuntu:24.04&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;ARG&lt;/span&gt; &lt;span class="nv"&gt;DEBIAN_FRONTEND&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;noninteractive&amp;#34;&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;ENV&lt;/span&gt; &lt;span class="nv"&gt;PACKAGES&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;ca-certificates libopengl0 libegl1 curl&amp;#34;&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;RUN&lt;/span&gt; apt-get update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; apt-get install --no-install-recommends -y &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;PACKAGES&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; apt-get clean &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; apt-get autoclean &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; apt-get autoremove &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; rm -rf /var/lib/apt/lists/*&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;COPY&lt;/span&gt; --from&lt;span class="o"&gt;=&lt;/span&gt;base --chown&lt;span class="o"&gt;=&lt;/span&gt;python:python /python /python&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;COPY&lt;/span&gt; --from&lt;span class="o"&gt;=&lt;/span&gt;base --chown&lt;span class="o"&gt;=&lt;/span&gt;app:app /usr/src/venv /usr/src/venv&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;COPY&lt;/span&gt; --from&lt;span class="o"&gt;=&lt;/span&gt;base --chown&lt;span class="o"&gt;=&lt;/span&gt;app:app /usr/src/app /usr/src/app&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;/usr/src/app&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;ENV&lt;/span&gt; &lt;span class="nv"&gt;PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;/usr/src/venv/bin:&lt;/span&gt;&lt;span class="nv"&gt;$PATH&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;RUN&lt;/span&gt; python ./minify_styles.py&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;8000&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;This was already using uv and multi-stage builds, which was good. But the production stage started from a fresh &lt;code&gt;ubuntu:24.04&lt;/code&gt;, which pulls in &lt;code&gt;apt&lt;/code&gt;, &lt;code&gt;dpkg&lt;/code&gt;, &lt;code&gt;systemd&lt;/code&gt; fragments, locales, and hundreds of other packages that a Python web service never touches. The &lt;code&gt;apt-get update &amp;amp;&amp;amp; apt-get install&lt;/code&gt; dance also meant the image wasn&amp;rsquo;t reproducible: two builds a week apart could get different package versions.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;COPY --from=ghcr.io/astral-sh/uv:latest&lt;/code&gt; was another problem. &lt;code&gt;latest&lt;/code&gt; means the uv version changes silently. If a new uv release changes lock file behavior or introduces a bug, your CI breaks with no obvious diff.&lt;/p&gt;
&lt;h2 id="switching-to-wolfi"&gt;Switching to Wolfi&lt;/h2&gt;
&lt;p&gt;Wolfi is a Linux distribution built specifically for containers. No package manager bloat, no shell login infrastructure, minimal base. Chainguard maintains it and publishes daily CVE-patched images.&lt;/p&gt;
&lt;p&gt;The new Dockerfile:&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;figcaption&gt;Dockerfile (Wolfi-based)&lt;/figcaption&gt;
&lt;div class="highlight" title="Dockerfile (Wolfi-based)"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-Dockerfile" data-lang="Dockerfile"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;cgr.dev/chainguard/wolfi-base&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;base&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;RUN&lt;/span&gt; apk add --no-cache &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; mesa-egl &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; mesa-gl &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; mesa-gbm &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; libgcc &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; libstdc++ &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; bash &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ca-certificates-bundle&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;ENV&lt;/span&gt; &lt;span class="nv"&gt;UV_COMPILE_BYTECODE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="nv"&gt;UV_LINK_MODE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;copy
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;ENV&lt;/span&gt; &lt;span class="nv"&gt;UV_PYTHON_INSTALL_DIR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/python&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;ENV&lt;/span&gt; &lt;span class="nv"&gt;UV_PYTHON_PREFERENCE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;only-managed&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;COPY&lt;/span&gt; --from&lt;span class="o"&gt;=&lt;/span&gt;ghcr.io/astral-sh/uv:0.9.18 /uv /uvx /bin/&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;RUN&lt;/span&gt; uv python install 3.12&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;/usr/src/app&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;ENV&lt;/span&gt; &lt;span class="nv"&gt;UV_PROJECT_ENVIRONMENT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/usr/src/venv&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;RUN&lt;/span&gt; --mount&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;cache,target&lt;span class="o"&gt;=&lt;/span&gt;/root/.cache/uv,id&lt;span class="o"&gt;=&lt;/span&gt;uv-base &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --mount&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;bind,source&lt;span class="o"&gt;=&lt;/span&gt;uv.lock,target&lt;span class="o"&gt;=&lt;/span&gt;uv.lock &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --mount&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;bind,source&lt;span class="o"&gt;=&lt;/span&gt;pyproject.toml,target&lt;span class="o"&gt;=&lt;/span&gt;pyproject.toml &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; uv sync --frozen --no-install-project --no-dev&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;COPY&lt;/span&gt; . /usr/src/app&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;RUN&lt;/span&gt; --mount&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;cache,target&lt;span class="o"&gt;=&lt;/span&gt;/root/.cache/uv,id&lt;span class="o"&gt;=&lt;/span&gt;uv-base &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; uv sync --frozen --no-dev&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;RUN&lt;/span&gt; /usr/src/venv/bin/python ./minify_styles.py&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;base&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;build&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;RUN&lt;/span&gt; --mount&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;cache,target&lt;span class="o"&gt;=&lt;/span&gt;/root/.cache/uv,id&lt;span class="o"&gt;=&lt;/span&gt;uv-build &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; uv sync --frozen&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;ENV&lt;/span&gt; &lt;span class="nv"&gt;PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;/usr/src/venv/bin:&lt;/span&gt;&lt;span class="nv"&gt;$PATH&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;ENV&lt;/span&gt; &lt;span class="nv"&gt;RUFF_CACHE_DIR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/tmp/.ruff_cache&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;ENV&lt;/span&gt; &lt;span class="nv"&gt;MYPY_CACHE_DIR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/tmp/.mypy_cache&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;ENV&lt;/span&gt; &lt;span class="nv"&gt;COVERAGE_FILE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/tmp/.coverage&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;base&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;prod&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;ENV&lt;/span&gt; &lt;span class="nv"&gt;PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;/usr/src/venv/bin:&lt;/span&gt;&lt;span class="nv"&gt;$PATH&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;ENV&lt;/span&gt; &lt;span class="nv"&gt;PYTHONUNBUFFERED&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;65532&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;8000&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;HEALTHCHECK&lt;/span&gt; --interval&lt;span class="o"&gt;=&lt;/span&gt;30s --timeout&lt;span class="o"&gt;=&lt;/span&gt;3s --start-period&lt;span class="o"&gt;=&lt;/span&gt;5s --retries&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; CMD &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;/usr/src/app/healthcheck.sh&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;A few things to unpack.&lt;/p&gt;
&lt;h3 id="three-stages-instead-of-two"&gt;Three stages instead of two&lt;/h3&gt;
&lt;p&gt;The old Dockerfile had &lt;code&gt;base&lt;/code&gt; (build everything) and a final production stage that copied artifacts. The new one has three:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Stage&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;th&gt;Packages&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;base&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Install runtime deps, sync production packages&lt;/td&gt;
&lt;td&gt;Runtime only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;build&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Extends base, adds dev deps for testing in CI&lt;/td&gt;
&lt;td&gt;Runtime + dev&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;prod&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Extends base, sets non-root user and healthcheck&lt;/td&gt;
&lt;td&gt;Runtime only&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The &lt;code&gt;build&lt;/code&gt; stage is what CI uses to run pytest, ruff, and mypy. The &lt;code&gt;prod&lt;/code&gt; stage is what ships. Both extend &lt;code&gt;base&lt;/code&gt;, so there&amp;rsquo;s no duplication of the dependency installation step.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;build&lt;/code&gt; stage redirects tool caches to &lt;code&gt;/tmp&lt;/code&gt; because the app directory is read-only for the non-root user:&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;figcaption&gt;Tool cache redirection for CI&lt;/figcaption&gt;
&lt;div class="highlight" title="Tool cache redirection for CI"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-Dockerfile" data-lang="Dockerfile"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;ENV&lt;/span&gt; &lt;span class="nv"&gt;RUFF_CACHE_DIR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/tmp/.ruff_cache&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;ENV&lt;/span&gt; &lt;span class="nv"&gt;MYPY_CACHE_DIR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/tmp/.mypy_cache&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;ENV&lt;/span&gt; &lt;span class="nv"&gt;COVERAGE_FILE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/tmp/.coverage&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;h3 id="pinned-uv-version"&gt;Pinned uv version&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;uv:latest&lt;/code&gt; became &lt;code&gt;uv:0.9.18&lt;/code&gt;. One line, but it eliminates an entire class of &amp;ldquo;works on my machine&amp;rdquo; issues. When we want to upgrade uv, we do it explicitly in a PR.&lt;/p&gt;
&lt;h3 id="non-root-production"&gt;Non-root production&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;prod&lt;/code&gt; stage runs as UID 65532, matching the convention from distroless images. The &lt;code&gt;base&lt;/code&gt; stage still runs as root (needed for &lt;code&gt;apk add&lt;/code&gt; and &lt;code&gt;uv python install&lt;/code&gt;), but the production image drops privileges before the entrypoint.&lt;/p&gt;
&lt;h3 id="minification-at-build-time"&gt;Minification at build time&lt;/h3&gt;
&lt;p&gt;The old Dockerfile ran &lt;code&gt;python ./minify_styles.py&lt;/code&gt; in the production stage at container startup (well, at build time of the final stage, but from a fresh Ubuntu). The new one runs it in the &lt;code&gt;base&lt;/code&gt; stage right after syncing dependencies. This means the minified assets are baked in and shared by both &lt;code&gt;build&lt;/code&gt; and &lt;code&gt;prod&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id="build-cache-isolation"&gt;Build cache isolation&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;--mount=type=cache&lt;/code&gt; directives got &lt;code&gt;id=uv-base&lt;/code&gt; and &lt;code&gt;id=uv-build&lt;/code&gt; suffixes. Without these, Docker can share cache mounts between stages that run concurrently in BuildKit, which causes lock contention.&lt;/p&gt;
&lt;h2 id="the-s5cmd-surprise"&gt;The s5cmd surprise&lt;/h2&gt;
&lt;p&gt;After deploying the Wolfi image, the tile generation job broke. It ran a shell script that downloaded &lt;code&gt;s5cmd&lt;/code&gt; (an S3-compatible file transfer tool) at runtime:&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;figcaption&gt;tile.sh (runtime download, broken)&lt;/figcaption&gt;
&lt;div class="highlight" title="tile.sh (runtime download, broken)"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt; ! &lt;span class="o"&gt;[&lt;/span&gt; -x &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;command&lt;/span&gt; -v s5cmd&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; curl -L &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; https://github.com/peak/s5cmd/releases/download/v2.2.2/s5cmd_2.2.2_Linux-64bit.tar.gz &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &amp;gt; s5cmd.tar.gz
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; tar -xzvf ./s5cmd.tar.gz --directory /usr/local/bin/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; rm ./s5cmd.tar.gz
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;fi&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;Two problems. First, &lt;code&gt;curl&lt;/code&gt; wasn&amp;rsquo;t in the image anymore; we&amp;rsquo;d removed it during the Wolfi migration because the application code doesn&amp;rsquo;t need it. Second, even if &lt;code&gt;curl&lt;/code&gt; was there, extracting to &lt;code&gt;/usr/local/bin/&lt;/code&gt; would fail because the container runs as non-root.&lt;/p&gt;
&lt;p&gt;The fix was one line in the Dockerfile and deleting the download script:&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;figcaption&gt;Installing s5cmd via apk&lt;/figcaption&gt;
&lt;div class="highlight" title="Installing s5cmd via apk"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-Dockerfile" data-lang="Dockerfile"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;RUN&lt;/span&gt; apk add --no-cache &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; mesa-egl &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; mesa-gl &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; mesa-gbm &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; libgcc &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; libstdc++ &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; bash &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; s5cmd &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ca-certificates-bundle&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;Wolfi has &lt;code&gt;s5cmd&lt;/code&gt; v2.3.0 in its package repository, newer than the v2.2.2 being downloaded. Installing it at build time is faster, more reliable, and gets security updates through the normal &lt;code&gt;apk&lt;/code&gt; channel.&lt;/p&gt;
&lt;p&gt;This is the kind of thing that falls out of a migration like this. Runtime downloads of binaries are a pattern that works on fat base images but breaks the moment you tighten things up. And they should break. Downloading unsigned tarballs into a running container is a supply chain risk hiding in plain sight.&lt;/p&gt;
&lt;h2 id="scan-results"&gt;Scan results&lt;/h2&gt;
&lt;p&gt;After the migration, Trivy against the production image:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Severity&lt;/th&gt;
&lt;th&gt;Before (Ubuntu)&lt;/th&gt;
&lt;th&gt;After (Wolfi)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;CRITICAL&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HIGH&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MEDIUM&lt;/td&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LOW&lt;/td&gt;
&lt;td&gt;27&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The Ubuntu image had 42 findings, all in OS packages we never used. The Wolfi image has zero because it only contains packages we explicitly installed. The Python dependency layer had 2 findings in both cases (a starlette version that needed bumping), but the OS layer went from noisy to clean.&lt;/p&gt;
&lt;p&gt;Dockle (Dockerfile best-practices linter) reports one false positive: it flags &lt;code&gt;settings.py&lt;/code&gt; as a potential credential file because of the filename. Suppressed with &lt;code&gt;dockle -af settings.py&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="what-made-it-work"&gt;What made it work&lt;/h2&gt;
&lt;p&gt;A few things that helped this go smoothly:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;uv&amp;rsquo;s dependency groups.&lt;/strong&gt; Separating dev tools from runtime dependencies in &lt;code&gt;pyproject.toml&lt;/code&gt; is what makes &lt;code&gt;--no-dev&lt;/code&gt; actually useful. Before, pytest and ruff shipped in production because they were in the same flat dependency list.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Wolfi&amp;rsquo;s package repository.&lt;/strong&gt; Having &lt;code&gt;s5cmd&lt;/code&gt;, Mesa GL libraries, and a recent &lt;code&gt;bash&lt;/code&gt; available via &lt;code&gt;apk&lt;/code&gt; meant we didn&amp;rsquo;t need to maintain custom installation scripts. The packages are rebuilt daily with CVE patches.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The three-stage pattern.&lt;/strong&gt; &lt;code&gt;base&lt;/code&gt; splitting into &lt;code&gt;build&lt;/code&gt; and &lt;code&gt;prod&lt;/code&gt; is now our standard for Python services. CI targets &lt;code&gt;build&lt;/code&gt;, production targets &lt;code&gt;prod&lt;/code&gt;, and both share the same dependency installation.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Pinning uv.&lt;/strong&gt; Sounds trivial but it removed a real source of non-determinism. &lt;code&gt;uv:latest&lt;/code&gt; in a Dockerfile is the same antipattern as &lt;code&gt;pip install package&lt;/code&gt; without a version: it works until it doesn&amp;rsquo;t, and you won&amp;rsquo;t know why.&lt;/p&gt;</description></item><item><title>Building a maps SDK from a Mapbox GL fork</title><link>https://man-you.ringum.net/posts/maps-v2/</link><pubDate>Sun, 08 Feb 2026 14:00:00 +0200</pubDate><guid>https://man-you.ringum.net/posts/maps-v2/</guid><description>&lt;p&gt;Rewriting the Woosmap Maps SDK on top of a Mapbox GL JS fork, with a Google Maps-compatible API surface, built with Bun, and an experiment in offscreen rendering.&lt;/p&gt;
&lt;h2 id="why"&gt;Why&lt;/h2&gt;
&lt;p&gt;The Woosmap Maps SDK depended on Mapbox GL JS. Then Mapbox changed the license. Version 2.0 moved to the Business Source License, not open source anymore. The last truly open version was 1.13.1 (December 2020). No more upstream bug fixes, no WebGL2 improvements, no new features. We were stuck on a frozen codebase.&lt;/p&gt;
&lt;p&gt;Staying on 1.13 forever wasn&amp;rsquo;t viable. Browser APIs move, WebGL evolves, security patches stop. Switching to Mapbox 2.x meant accepting the BSL and its usage restrictions. MapLibre forked from the same 1.13 base, but taking a dependency on another project&amp;rsquo;s roadmap puts you in the same position, just with different maintainers.&lt;/p&gt;
&lt;p&gt;So we forked 1.13.1 ourselves, ported the whole thing to TypeScript, and built a Google Maps-compatible API wrapper on top. Our customers use the Google Maps API surface: &lt;code&gt;new woosmap.map.Map()&lt;/code&gt;, &lt;code&gt;woosmap.map.Marker&lt;/code&gt;, &lt;code&gt;woosmap.map.event.addListener&lt;/code&gt;. Switching to a Mapbox-style API would break every integration. One codebase, two API surfaces: Mapbox GL rendering engine underneath, Google Maps API on top.&lt;/p&gt;
&lt;h2 id="architecture"&gt;Architecture&lt;/h2&gt;
&lt;p&gt;Two layers, cleanly separated.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Mapbox GL fork&lt;/strong&gt; (&lt;code&gt;src/mapbox-gl/&lt;/code&gt;): the rendering engine. WebGL2 context management, vector tile decoding, style evaluation, symbol placement with collision detection, gesture handling. 57 GLSL shaders, vertex and fragment pairs for fill, line, symbol, heatmap, hillshade, raster, extrusion. The full Mapbox style spec expression engine: &lt;code&gt;get&lt;/code&gt;, &lt;code&gt;has&lt;/code&gt;, &lt;code&gt;match&lt;/code&gt;, &lt;code&gt;case&lt;/code&gt;, &lt;code&gt;interpolate&lt;/code&gt;, &lt;code&gt;step&lt;/code&gt;, &lt;code&gt;let/var&lt;/code&gt;, &lt;code&gt;coalesce&lt;/code&gt;, arithmetic, string ops, comparisons.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Woosmap layer&lt;/strong&gt; (&lt;code&gt;src/woosmap/&lt;/code&gt;): the Google Maps compatibility wrapper. &lt;code&gt;Map&lt;/code&gt;, &lt;code&gt;Marker&lt;/code&gt;, &lt;code&gt;InfoWindow&lt;/code&gt;, &lt;code&gt;OverlayView&lt;/code&gt;, &lt;code&gt;Data&lt;/code&gt; layer, pane management, gesture handling overlay. Plus service clients: stores, distance matrix, localities autocomplete, datasets, transit, directions.&lt;/p&gt;
&lt;p&gt;The Woosmap &lt;code&gt;Map&lt;/code&gt; class wraps the Mapbox &lt;code&gt;Map&lt;/code&gt;:&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;figcaption&gt;src/woosmap/map.ts&lt;/figcaption&gt;
&lt;div class="highlight" title="src/woosmap/map.ts"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kr"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;MapBoxMap&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="kr"&gt;from&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;../mapbox-gl/ui/map&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;class&lt;/span&gt; &lt;span class="nx"&gt;Map&lt;/span&gt; &lt;span class="kr"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;MVCObject&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;_mapboxMap&lt;/span&gt;: &lt;span class="kt"&gt;MapBoxMap&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;element&lt;/span&gt;: &lt;span class="kt"&gt;HTMLElement&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;: &lt;span class="kt"&gt;MapOptions&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// Create the Mapbox map internally
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_mapboxMap&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;MapBoxMap&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;container&lt;/span&gt;: &lt;span class="kt"&gt;element&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// Wire up Google Maps-style events, panes, controls
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;Properties fire change events automatically through &lt;code&gt;MVCObject&lt;/code&gt;. Set &lt;code&gt;marker.position&lt;/code&gt; and &lt;code&gt;position_changed&lt;/code&gt; fires. Same pattern Google Maps uses.&lt;/p&gt;
&lt;h2 id="the-style-system"&gt;The style system&lt;/h2&gt;
&lt;p&gt;Woosmap customers use Google Maps-style JSON rules to customize maps. Hue shifts, saturation, lightness, gamma correction, invert, applied hierarchically by feature type.&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;figcaption&gt;src/woosmap/map-style/map-style.ts&lt;/figcaption&gt;
&lt;div class="highlight" title="src/woosmap/map-style/map-style.ts"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;MapStyler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;color?&lt;/span&gt;: &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// hex color
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;hue?&lt;/span&gt;: &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// extract hue, keep lightness/saturation
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;saturation?&lt;/span&gt;: &lt;span class="kt"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// shift saturation
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;lightness?&lt;/span&gt;: &lt;span class="kt"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// shift lightness
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;gamma?&lt;/span&gt;: &lt;span class="kt"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// gamma correction on lightness
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;invert_lightness?&lt;/span&gt;: &lt;span class="kt"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;visibility?&lt;/span&gt;: &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// &amp;#34;on&amp;#34; | &amp;#34;off&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;weight?&lt;/span&gt;: &lt;span class="kt"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// line/label weight
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;};&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;A &lt;code&gt;LayerRegistry&lt;/code&gt; maps Google&amp;rsquo;s feature type hierarchy (&lt;code&gt;road.highway&lt;/code&gt;, &lt;code&gt;poi.park&lt;/code&gt;, &lt;code&gt;water&lt;/code&gt;) to Mapbox GL layer IDs. Style rules walk the tree, match layers, apply HSL transforms. The result: customers keep their existing style configurations, the renderer is completely different underneath.&lt;/p&gt;
&lt;p&gt;Styles load dynamically from the Woosmap API via &lt;code&gt;StyleFetcher&lt;/code&gt;, not baked into the bundle. Change your style server-side, maps update without redeployment.&lt;/p&gt;
&lt;h2 id="three-bundles"&gt;Three bundles&lt;/h2&gt;
&lt;p&gt;Not every integration needs a full map. Some just need geocoding or store search. Building one monolithic bundle wastes bandwidth.&lt;/p&gt;
&lt;p&gt;Bun builds three separate bundles from three entry points:&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;figcaption&gt;scripts/builder.ts&lt;/figcaption&gt;
&lt;div class="highlight" title="scripts/builder.ts"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Full SDK: map + services + worker
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;Bun&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;build&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;entrypoints&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;./src/maps.ts&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;src/worker.ts&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;src/painter.ts&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;splitting&lt;/span&gt;: &lt;span class="kt"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;minify&lt;/span&gt;: &lt;span class="kt"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;sourcemap&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;linked&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;loader&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;.glsl&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;text&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Services only: API clients, no WebGL
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;Bun&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;build&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;entrypoints&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;src/services.ts&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Localities: place autocomplete widget
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;Bun&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;build&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;entrypoints&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;src/localities.ts&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;GLSL shaders load as text strings via Bun&amp;rsquo;s loader plugin. No webpack shader-loader, no build-time compilation. The shader source gets inlined in the bundle and compiled at runtime by WebGL.&lt;/p&gt;
&lt;p&gt;Environment configuration (&lt;code&gt;local&lt;/code&gt;, &lt;code&gt;staging&lt;/code&gt;, &lt;code&gt;production&lt;/code&gt;) injects API endpoints at build time through &lt;code&gt;define&lt;/code&gt;:&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;retVal&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sb"&gt;`Bun.env.API_BASE_URL`&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;API_BASE_URL&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;retVal&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sb"&gt;`Bun.env.ASSETS_BASE_URL`&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ASSETS_BASE_URL&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;h2 id="worker-architecture"&gt;Worker architecture&lt;/h2&gt;
&lt;p&gt;Tile processing is expensive: protobuf decoding, geometry clipping, feature indexing. Doing it on the main thread blocks interactions.&lt;/p&gt;
&lt;p&gt;The SDK spins up web workers from a blob URL constructed at load time:&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;figcaption&gt;src/maps.ts&lt;/figcaption&gt;
&lt;div class="highlight" title="src/maps.ts"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;scriptURL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kr"&gt;import&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;maps.js&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;worker.js&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sb"&gt;`import &amp;#34;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;scriptURL&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;&amp;#34;;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;blob&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Blob&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="kr"&gt;type&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;application/javascript&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;workerUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;createObjectURL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;blob&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;Workers handle tile deserialization and geometry processing. The main thread handles rendering, events, and camera updates. Communication is message-based. Tiles arrive asynchronously, trigger geometry extraction, and the painter picks them up on the next frame.&lt;/p&gt;
&lt;h2 id="offscreen-rendering-experiment"&gt;Offscreen rendering experiment&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;maps-offscreen.ts&lt;/code&gt; entry point pushes further: move the entire WebGL renderer into a worker using &lt;code&gt;OffscreenCanvas&lt;/code&gt; + &lt;code&gt;comlink&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;OnscreenMap&lt;/code&gt; lives on the main thread: handles DOM events, gesture recognition, camera state. The &lt;code&gt;OffscreenMap&lt;/code&gt; lives in a worker: owns the WebGL context, runs the painter, processes tiles. Camera updates flow from main thread to worker, rendered frames appear on the transferred canvas.&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;figcaption&gt;src/woosmap/onscreen-map.ts&lt;/figcaption&gt;
&lt;div class="highlight" title="src/woosmap/onscreen-map.ts"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;class&lt;/span&gt; &lt;span class="nx"&gt;OnscreenMap&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;offscreenMap&lt;/span&gt;: &lt;span class="kt"&gt;Remote&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;OffscreenMap&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;handlers&lt;/span&gt;: &lt;span class="kt"&gt;HandlerManager&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;transform&lt;/span&gt;: &lt;span class="kt"&gt;Transform&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;element&lt;/span&gt;: &lt;span class="kt"&gt;HTMLElement&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;options?&lt;/span&gt;: &lt;span class="kt"&gt;object&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;canvas&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;canvas&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;offscreen&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;transferControlToOffscreen&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// Transfer canvas to worker, all GL happens there
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;The tricky part is camera update batching. Every pan/scroll event would trigger a worker message, overwhelming the channel. The solution: batch camera updates with a timer and &lt;code&gt;requestAnimationFrame&lt;/code&gt;, send at most one update per frame interval. Immediate sends for significant zoom changes, deferred sends for continuous panning.&lt;/p&gt;
&lt;p&gt;Still experimental. Safari&amp;rsquo;s &lt;code&gt;OffscreenCanvas&lt;/code&gt; support has quirks, and synchronizing the transform state between threads without jank requires careful debouncing.&lt;/p&gt;
&lt;h2 id="symbol-placement"&gt;Symbol placement&lt;/h2&gt;
&lt;p&gt;Text labels on maps are deceptively hard. The Mapbox GL fork handles:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Glyph atlasing&lt;/strong&gt;: PBF glyph format, dynamically growing texture atlas (1024x1024 initial, doubles when full), SDF rendering for clean scaling at any size&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Collision detection&lt;/strong&gt;: grid-based spatial index, labels grouped by feature (icon + text placed together, all-or-nothing), sorted by layer priority and &lt;code&gt;symbol-sort-key&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cross-tile continuity&lt;/strong&gt;: &lt;code&gt;CrossTileSymbolIndex&lt;/code&gt; maintains label placement across tile boundaries so labels don&amp;rsquo;t pop in and out when panning&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Variable anchor&lt;/strong&gt;: if a label&amp;rsquo;s primary anchor collides, try alternatives. Pre-compute screen positions for each variant, test in order&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CJK text&lt;/strong&gt;: local ideograph font families bypass the glyph server for Chinese, Japanese, Korean characters. Properly sized using browser font metrics&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The symbol size evaluation is complex. &lt;code&gt;text-size&lt;/code&gt; gets evaluated at five different zoom levels per feature for layout, collision boxes, line placement, and shader interpolation.&lt;/p&gt;
&lt;p&gt;One practical fix we added: a maximum label count per bucket to prevent repeated line labels from overflowing. Road names on long highways would generate hundreds of labels. Now they&amp;rsquo;re capped.&lt;/p&gt;
&lt;h2 id="services-integration"&gt;Services integration&lt;/h2&gt;
&lt;p&gt;The Woosmap layer isn&amp;rsquo;t just rendering. It includes API clients for the full Woosmap platform:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;StoresService&lt;/strong&gt;: store locator with search, autocomplete, radius queries&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;DistanceService&lt;/strong&gt;: matrix routing, isochrones, multiple travel modes (driving, walking, cycling)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;LocalitiesService&lt;/strong&gt;: place autocomplete supporting 70+ languages&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;DatasetService&lt;/strong&gt;: query and overlay custom geospatial datasets&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;TransitService&lt;/strong&gt;: public transportation routing&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;DirectionsService&lt;/strong&gt;: turn-by-turn directions with renderer&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Each service handles its own error taxonomy (&lt;code&gt;INVALID_REQUEST&lt;/code&gt;, &lt;code&gt;REQUEST_DENIED&lt;/code&gt;, &lt;code&gt;OVER_QUERY_LIMIT&lt;/code&gt;, &lt;code&gt;MAX_ELEMENTS_EXCEEDED&lt;/code&gt;) and throws typed errors (&lt;code&gt;BadRequestError&lt;/code&gt;, &lt;code&gt;ForbiddenError&lt;/code&gt;, &lt;code&gt;TooManyRequestsError&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;Overlays (&lt;code&gt;StoresOverlay&lt;/code&gt;, &lt;code&gt;DatasetsOverlay&lt;/code&gt;, &lt;code&gt;DirectionsRenderer&lt;/code&gt;) connect service responses directly to map layers. Fetch stores, get markers. Compute a route, get a polyline.&lt;/p&gt;
&lt;h2 id="testing"&gt;Testing&lt;/h2&gt;
&lt;p&gt;97 test files. Vitest with Playwright for browser testing. The WebGL context needs a real browser, not jsdom.&lt;/p&gt;
&lt;p&gt;Handler tests cover the full gesture matrix: mouse drag pan, scroll zoom, keyboard navigation, touch zoom/rotate, double-click zoom, box zoom. Each handler type has its own test file, testing event sequences and resulting transform states.&lt;/p&gt;
&lt;p&gt;Style-spec tests validate the expression engine: filter compilation, color space conversions (RGB, HSL, LAB), interpolation, type coercion. These are ported from the Mapbox GL test suite and adapted for the TypeScript rewrite.&lt;/p&gt;
&lt;p&gt;Visual regression tests capture screenshots and diff against baselines. Useful for catching rendering changes across the 57 shaders.&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;bun run test:browser &lt;span class="c1"&gt;# Vitest + Playwright (Chromium)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;npx biome check src &lt;span class="c1"&gt;# Lint&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;h2 id="whats-there"&gt;What&amp;rsquo;s there&lt;/h2&gt;
&lt;p&gt;The basics are solid. Fill, line, symbol, raster, background, heatmap, hillshade, fill-extrusion layers all render. The full style expression engine works. Collision detection, variable anchor, text wrapping, cross-tile symbol continuity. Markers with labels, info windows, data layer with per-feature styling. All five service clients.&lt;/p&gt;
&lt;p&gt;536 TypeScript files, 57 GLSL shaders. The Google Maps API compatibility layer means existing Woosmap integrations can switch renderers without code changes.&lt;/p&gt;
&lt;p&gt;What&amp;rsquo;s still rough:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Offscreen rendering&lt;/strong&gt; works but the camera synchronization needs more tuning for smooth panning on slower devices&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Complex text shaping&lt;/strong&gt; is 1:1 codepoint-to-glyph. Arabic, Thai, and other complex scripts need a proper shaper&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Bundle size&lt;/strong&gt; could be smaller with better tree-shaking of the Mapbox GL fork&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Line rendering&lt;/strong&gt; has no dash patterns or gradients yet&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;About 5MB of TypeScript source. The output bundles are significantly smaller after minification and tree-shaking.&lt;/p&gt;</description></item><item><title>A map style compiler in Python</title><link>https://man-you.ringum.net/posts/map-style-compiler/</link><pubDate>Sat, 07 Feb 2026 16:00:00 +0200</pubDate><guid>https://man-you.ringum.net/posts/map-style-compiler/</guid><description>&lt;p&gt;&lt;figure&gt;
&lt;img src="https://man-you.ringum.net/posts/map-style-compiler/elzar.svg" alt="Elzar" /&gt;
&lt;figcaption&gt;BAM!&lt;/figcaption&gt;
&lt;/figure&gt;&lt;/p&gt;
&lt;p&gt;Generating Mapbox GL GL style JSON from Python code instead of editing 3,000-line JSON files by hand. Hierarchical style resolution, a Django-style filter DSL, injection-based layer ordering, and a Go sprite generator called from Python via ctypes.&lt;/p&gt;
&lt;h2 id="the-problem-with-style-json"&gt;The problem with style JSON&lt;/h2&gt;
&lt;p&gt;A Mapbox GL GL style is a single JSON file that describes everything about how a map looks. Background color, road widths at every zoom level, label fonts, icon placement, landcover tints, building extrusion heights. All of it.&lt;/p&gt;
&lt;p&gt;A real production style runs to thousands of lines. Ours had over 150 layers. Roads alone need six layer variants each (tunnel casing, tunnel fill, road casing, road fill, bridge casing, bridge fill), and there are eight road classes. That&amp;rsquo;s 48 road layers before you count labels, oneway arrows, or rail.&lt;/p&gt;
&lt;p&gt;Editing this by hand is slow and error-prone. Change the motorway color and you need to find it in six places. Add a new road class and you need to insert layers at exactly the right positions between tunnels and bridges. Reorder a layer and you break z-ordering for everything above it.&lt;/p&gt;
&lt;h2 id="python-instead-of-json"&gt;Python instead of JSON&lt;/h2&gt;
&lt;p&gt;The style compiler replaces hand-edited JSON with Python code. You define layers, colors, filters, and style properties in Python. The compiler resolves everything and emits a Mapbox GL GL style JSON file.&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;python -m map_style --style&lt;span class="o"&gt;=&lt;/span&gt;streets -o style/streets_classic.json&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;The output is a standard Mapbox GL GL style: the renderer doesn&amp;rsquo;t know or care that it was generated. But the source is modular, typed, and DRY.&lt;/p&gt;
&lt;h2 id="layer-definitions"&gt;Layer definitions&lt;/h2&gt;
&lt;p&gt;A road is defined once. &lt;code&gt;ClassicRoadDataNode&lt;/code&gt; generates all six variants:&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;figcaption&gt;streets_classic/roads.py&lt;/figcaption&gt;
&lt;div class="highlight" title="streets_classic/roads.py"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;ClassicRoadDataNode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;motorway&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;classes&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;motorway&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;feature_type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;road.highway.motorway&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;extra_filter&lt;/span&gt;&lt;span class="o"&gt;=~&lt;/span&gt;&lt;span class="n"&gt;Q&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ramp__eq&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;casing_feature_type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;road.highway.motorway&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;This produces:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;tunnel_motorway_casing&lt;/code&gt;: tunnel stroke&lt;/li&gt;
&lt;li&gt;&lt;code&gt;tunnel_motorway&lt;/code&gt;: tunnel fill&lt;/li&gt;
&lt;li&gt;&lt;code&gt;road_motorway_casing&lt;/code&gt;: road stroke&lt;/li&gt;
&lt;li&gt;&lt;code&gt;road_motorway&lt;/code&gt;: road fill&lt;/li&gt;
&lt;li&gt;&lt;code&gt;bridge_motorway_casing&lt;/code&gt;: bridge stroke&lt;/li&gt;
&lt;li&gt;&lt;code&gt;bridge_motorway&lt;/code&gt;: bridge fill&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Each variant gets the correct brunnel filter automatically. Tunnel layers filter on &lt;code&gt;brunnel == &amp;quot;tunnel&amp;quot;&lt;/code&gt;, bridge on &lt;code&gt;brunnel == &amp;quot;bridge&amp;quot;&lt;/code&gt;, normal roads on &lt;code&gt;brunnel not in [&amp;quot;bridge&amp;quot;, &amp;quot;tunnel&amp;quot;]&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Rail works the same way: &lt;code&gt;ClassicRailDataNode&lt;/code&gt; generates tunnel, road, and bridge variants with optional hatching layers for the cross-ties pattern.&lt;/p&gt;
&lt;h2 id="injection-points"&gt;Injection points&lt;/h2&gt;
&lt;p&gt;With 150+ layers, ordering is everything. Roads must render above landcover. Bridges must render above buildings. Labels above everything. Getting one layer out of place breaks the visual hierarchy.&lt;/p&gt;
&lt;p&gt;Instead of maintaining an explicit ordered list, the system uses injection points: named slots that define the rendering order:&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;figcaption&gt;generator/data.py&lt;/figcaption&gt;
&lt;div class="highlight" title="generator/data.py"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;InjectionsNames&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Enum&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;TUNNEL_ROAD_CASING&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;tunnel_road_casing&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;TUNNEL_ROAD&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;tunnel_road&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;ROAD_AREA&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;road_area&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;ROAD_CASING&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;road_casing&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;ROAD&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;road&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;BUILDING&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;_building_injection&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;BRIDGE_ROAD_CASING&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;bridge_road_casing&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;BRIDGE_ROAD&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;bridge_road&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;BUILDING_3D&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;building_3d&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;LABEL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;labels&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;POI_SYMBOL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;poi_symbol&amp;#34;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;Layers insert themselves before a named injection point:&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;stack&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_layer_before&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;InjectionsNames&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ROAD&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Layer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;road_motorway&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;stack&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_layer_before&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;InjectionsNames&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BRIDGE_ROAD&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Layer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;bridge_motorway&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;The &lt;code&gt;Stack&lt;/code&gt; maintains the order. Injection points themselves are stripped from the final output. They&amp;rsquo;re scaffolding, not layers. Adding a new road class doesn&amp;rsquo;t require knowing the index of every other layer. You just say &amp;ldquo;this goes in the ROAD group&amp;rdquo; and the system handles placement.&lt;/p&gt;
&lt;h2 id="filter-dsl"&gt;Filter DSL&lt;/h2&gt;
&lt;p&gt;Mapbox GL filters are nested JSON arrays. Writing them by hand is unreadable:&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;all&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;==&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;get&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;class&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;motorway&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;!&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;in&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;get&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;brunnel&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;literal&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;bridge&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;tunnel&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]]]]]&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;The compiler uses Django-style Q objects instead:&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;Q&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;class__eq&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;motorway&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;~&lt;/span&gt;&lt;span class="n"&gt;Q&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;brunnel__in&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;bridge&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;tunnel&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;&lt;code&gt;&amp;amp;&lt;/code&gt; means AND, &lt;code&gt;|&lt;/code&gt; means OR, &lt;code&gt;~&lt;/code&gt; means NOT. The &lt;code&gt;ExpressionSet&lt;/code&gt; class compiles Q trees to Mapbox GL expressions:&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;figcaption&gt;generator/filters.py&lt;/figcaption&gt;
&lt;div class="highlight" title="generator/filters.py"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;OPERATORS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;eq&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;==&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;neq&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;!=&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;gt&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&amp;gt;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;gte&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&amp;gt;=&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;lt&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&amp;lt;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;lte&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&amp;lt;=&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Q(class__in=[&amp;#34;motorway&amp;#34;]) becomes:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# [&amp;#34;in&amp;#34;, [&amp;#34;get&amp;#34;, &amp;#34;class&amp;#34;], [&amp;#34;literal&amp;#34;, [&amp;#34;motorway&amp;#34;]]]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Q(brunnel__exists=False) becomes:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# [&amp;#34;!&amp;#34;, [&amp;#34;has&amp;#34;, &amp;#34;brunnel&amp;#34;]]&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;Filters compose naturally. The ramp filter for motorway links:&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;Q&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;class__in&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;motorway&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;Q&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ramp__eq&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;The non-ramp filter for main motorways:&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;Q&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;class__in&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;motorway&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;~&lt;/span&gt;&lt;span class="n"&gt;Q&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ramp__eq&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;The &lt;code&gt;Case&lt;/code&gt; class builds conditional expressions for layers that vary by feature class, like different icon colors per POI type:&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Case&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;restaurant&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Q&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;class__eq&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;restaurant&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;hospital&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Q&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;class__eq&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;hospital&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)})&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;case&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;restaurant&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;#e74c3c&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;hospital&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;#3498db&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;#666&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# [&amp;#34;case&amp;#34;, [&amp;#34;==&amp;#34;, [&amp;#34;get&amp;#34;, &amp;#34;class&amp;#34;], &amp;#34;restaurant&amp;#34;], &amp;#34;#e74c3c&amp;#34;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# [&amp;#34;==&amp;#34;, [&amp;#34;get&amp;#34;, &amp;#34;class&amp;#34;], &amp;#34;hospital&amp;#34;], &amp;#34;#3498db&amp;#34;, &amp;#34;#666&amp;#34;]&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;h2 id="hierarchical-style-resolution"&gt;Hierarchical style resolution&lt;/h2&gt;
&lt;p&gt;A &lt;code&gt;StyleTree&lt;/code&gt; organizes paint and layout properties in a dot-separated hierarchy. Child selectors inherit from parents:&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;figcaption&gt;streets_classic/style.py&lt;/figcaption&gt;
&lt;div class="highlight" title="streets_classic/style.py"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;styles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;road&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;StyleElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;paint&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;LinePaint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;line_cap&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;round&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;road.highway&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;StyleElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;paint&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;LinePaint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;line_color&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;ROAD_HIGHWAY_FILL_COLOR&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;line_width&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;road.highway.motorway&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;StyleElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;paint&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;LinePaint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;line_width&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;Resolving &lt;code&gt;road.highway.motorway&lt;/code&gt; walks the tree from root to leaf, collecting all &lt;code&gt;StyleElement&lt;/code&gt; instances along the path. Properties merge: the motorway gets &lt;code&gt;line_cap=&amp;quot;round&amp;quot;&lt;/code&gt; from &lt;code&gt;road&lt;/code&gt;, &lt;code&gt;line_color=&amp;quot;#fc8&amp;quot;&lt;/code&gt; from &lt;code&gt;road.highway&lt;/code&gt;, and &lt;code&gt;line_width=6&lt;/code&gt; from its own node.&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;figcaption&gt;generator/styler.py&lt;/figcaption&gt;
&lt;div class="highlight" title="generator/styler.py"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;selector_path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;StyleElement&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;nodes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_node_path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;selector_path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;elements&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;StyleElement&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;nodes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;elements&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;elements&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;elements&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;Same pattern as CSS cascade, but explicit. You see exactly what inherits from where by reading the selector paths. No specificity wars.&lt;/p&gt;
&lt;h2 id="colors"&gt;Colors&lt;/h2&gt;
&lt;p&gt;The color module parses hex, RGB, RGBA, HSL, and HTML color names. More usefully, it does HSL-based transformations:&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;figcaption&gt;generator/color.py&lt;/figcaption&gt;
&lt;div class="highlight" title="generator/color.py"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;Color&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;#fc8&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;darken&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;0.3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;to_rgba&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Shifts lightness down by 30%, used for deriving label colors from fill colors&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;All colors live in one file per style:&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;figcaption&gt;streets_classic/colors.py&lt;/figcaption&gt;
&lt;div class="highlight" title="streets_classic/colors.py"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;BACKGROUND_COLOR&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;rgba(252, 247, 229, 1)&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;WATER_COLOR&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;rgba(134, 204, 250, 1)&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;ROAD_HIGHWAY_FILL_COLOR&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;#fc8&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;ROAD_HIGHWAY_STROKE_COLOR&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;#e9ac77&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;ROAD_MINOR_FILL_COLOR&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;#fff&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;ROAD_MINOR_STROKE_COLOR&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;hsl(35, 6%, 80%)&amp;#34;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;Change a color constant, regenerate, and every layer using it updates. No grep through JSON.&lt;/p&gt;
&lt;h2 id="pydantic-models-for-the-style-spec"&gt;Pydantic models for the style spec&lt;/h2&gt;
&lt;p&gt;Every Mapbox GL layer type has a corresponding Pydantic v2 model with strict validation:&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;figcaption&gt;generator/mapbox_style.py&lt;/figcaption&gt;
&lt;div class="highlight" title="generator/mapbox_style.py"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;LinePaint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BaseModel&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;model_config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ConfigDict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;populate_by_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;line_color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;alias&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;line-color&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;line_width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;alias&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;line-width&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;line_opacity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;alias&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;line-opacity&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;line_dasharray&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;alias&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;line-dasharray&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;line_cap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;alias&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;line-cap&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# ...&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;The &lt;code&gt;alias&lt;/code&gt; parameter handles the mismatch between Python identifiers (underscores) and Mapbox GL property names (hyphens). &lt;code&gt;line_color&lt;/code&gt; in Python becomes &lt;code&gt;line-color&lt;/code&gt; in JSON output.&lt;/p&gt;
&lt;p&gt;Pydantic catches type errors at definition time, not when the renderer fails to parse the JSON. Pass a string where a number is expected and you get an immediate error.&lt;/p&gt;
&lt;h2 id="sprite-generation"&gt;Sprite generation&lt;/h2&gt;
&lt;p&gt;Map icons (POI markers, highway shields, oneway arrows) are packed into a sprite sheet: a single PNG with a JSON manifest mapping icon names to pixel regions.&lt;/p&gt;
&lt;p&gt;The sprite generator is written in Go for performance. Python calls it through ctypes:&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;figcaption&gt;generator/sprite.py&lt;/figcaption&gt;
&lt;div class="highlight" title="generator/sprite.py"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;lib&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ctypes&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CDLL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lib_path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GenerateSprite&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argtypes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ctypes&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;c_char_p&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ctypes&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;c_char_p&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ctypes&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;c_int&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GenerateSprite&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;restype&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ctypes&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;c_char_p&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GenerateSprite&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;icon_library_path&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;output_path&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;pixel_ratio&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;# 1 for sprite.png, 2 for sprite@2x.png&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;The Go library handles SVG rasterization, SDF (signed distance field) generation for runtime-colorable icons, bin-packing into a texture atlas, and both 1x and 2x output.&lt;/p&gt;
&lt;p&gt;Template-based icon names resolve from filter context. When a layer uses &lt;code&gt;icon-image: &amp;quot;{class}_15&amp;quot;&lt;/code&gt;, the compiler extracts which &lt;code&gt;class&lt;/code&gt; values the filter allows and registers each concrete icon (&lt;code&gt;restaurant_15&lt;/code&gt;, &lt;code&gt;hospital_15&lt;/code&gt;, etc.) for sprite generation.&lt;/p&gt;
&lt;h2 id="poi-metadata"&gt;POI metadata&lt;/h2&gt;
&lt;p&gt;POI layers carry metadata beyond what Mapbox GL needs. The Woosmap API uses it to support dynamic styling at runtime:&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;POIMetadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;symbol_color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;text_color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;icon&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;visible&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;This metadata embeds in the layer&amp;rsquo;s &lt;code&gt;metadata&lt;/code&gt; field (which Mapbox GL ignores but passes through). The API can query which POI categories exist, their colors, and their visibility, without parsing the style expressions.&lt;/p&gt;
&lt;p&gt;Feature types follow a hierarchy: &lt;code&gt;poi.school&lt;/code&gt;, &lt;code&gt;poi.sports_complex&lt;/code&gt;, &lt;code&gt;transit.station.rail&lt;/code&gt;. The &lt;code&gt;LayerRegistry&lt;/code&gt; maps these to the concrete Mapbox GL layers they affect, enabling Google Maps-style feature type styling through the Woosmap SDK.&lt;/p&gt;
&lt;h2 id="multiple-styles"&gt;Multiple styles&lt;/h2&gt;
&lt;p&gt;The architecture supports multiple style variants from the same infrastructure. &lt;code&gt;streets_classic&lt;/code&gt; is the production style. &lt;code&gt;streets_satellite&lt;/code&gt; reuses the same layer definitions, road nodes, and filter DSL with a different color palette tuned for satellite imagery overlay: higher contrast labels, semi-transparent fills, no background color.&lt;/p&gt;
&lt;p&gt;Adding a new style variant means writing a new color file and adjusting a few layer properties. The layer structure, injection points, and filter expressions stay the same.&lt;/p&gt;
&lt;h2 id="what-this-gets-you"&gt;What this gets you&lt;/h2&gt;
&lt;p&gt;The streets_classic style has ~150 renderable layers. The Python source is organized across about a dozen files: colors, roads, mapping, style tree, POI definitions. Each file is under 300 lines.&lt;/p&gt;
&lt;p&gt;The equivalent hand-maintained JSON was one file, thousands of lines, with duplicated filter expressions and no inheritance. Every road color change required finding and editing six layer definitions. Every new road class required inserting layers at the correct positions relative to all other layers.&lt;/p&gt;
&lt;p&gt;Now it&amp;rsquo;s: change a constant in &lt;code&gt;colors.py&lt;/code&gt;, run the compiler, get a valid style JSON. Type-checked, with the layer ordering handled by injection points instead of manual index management.&lt;/p&gt;</description></item><item><title>Tunes</title><link>https://man-you.ringum.net/posts/tunes/</link><pubDate>Tue, 01 Jul 2025 10:31:38 +0200</pubDate><guid>https://man-you.ringum.net/posts/tunes/</guid><description>&lt;p&gt;iTunes Match worked great for years: you uploaded your library, Apple matched what it could, and you had access to everything from any device. But Apple is clearly moving everyone toward Apple Music, and iTunes Match has been slowly rotting. The writing was on the wall.&lt;/p&gt;
&lt;p&gt;I didn&amp;rsquo;t want to subscribe to Apple Music. I wanted to keep my library, my ratings, my play counts, my playlists, all the metadata accumulated over 20 years. So I built my own streaming setup: a Go backend that serves the library over HTTP, and SwiftUI apps that play it on iOS, macOS, and tvOS.&lt;/p&gt;
&lt;h2 id="exporting-the-library"&gt;Exporting the library&lt;/h2&gt;
&lt;p&gt;Apple Music (formerly iTunes) can export the library as an XML plist file. It&amp;rsquo;s a flat dictionary of tracks keyed by ID, with every field you&amp;rsquo;d expect: name, artist, album, play count, rating, date added, file location, and about 60 more.&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;figcaption&gt;A track entry in the exported XML&lt;/figcaption&gt;
&lt;div class="highlight" title="A track entry in the exported XML"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-xml" data-lang="xml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;1234&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;&amp;lt;dict&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;Track ID&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&amp;lt;integer&amp;gt;&lt;/span&gt;1234&lt;span class="nt"&gt;&amp;lt;/integer&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;Name&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&amp;lt;string&amp;gt;&lt;/span&gt;Windowlicker&lt;span class="nt"&gt;&amp;lt;/string&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;Artist&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&amp;lt;string&amp;gt;&lt;/span&gt;Aphex Twin&lt;span class="nt"&gt;&amp;lt;/string&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;Album&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&amp;lt;string&amp;gt;&lt;/span&gt;Windowlicker&lt;span class="nt"&gt;&amp;lt;/string&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;Total Time&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&amp;lt;integer&amp;gt;&lt;/span&gt;381573&lt;span class="nt"&gt;&amp;lt;/integer&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;Location&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&amp;lt;string&amp;gt;&lt;/span&gt;file:///Music/Aphex%20Twin/Windowlicker/01%20Windowlicker.m4a&lt;span class="nt"&gt;&amp;lt;/string&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;&amp;lt;/dict&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;The Go struct that maps to this uses &lt;code&gt;plist&lt;/code&gt; tags for XML parsing and &lt;code&gt;gorm&lt;/code&gt; tags for database storage, with every optional field as a pointer for proper nil handling:&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;figcaption&gt;Track model (abbreviated)&lt;/figcaption&gt;
&lt;div class="highlight" title="Track model (abbreviated)"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ITunesTrack&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;TrackID&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;`plist:&amp;#34;Track ID&amp;#34; json:&amp;#34;track_id&amp;#34; gorm:&amp;#34;primaryKey&amp;#34;`&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;`plist:&amp;#34;Name&amp;#34; json:&amp;#34;name&amp;#34; gorm:&amp;#34;index&amp;#34;`&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Artist&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;`plist:&amp;#34;Artist&amp;#34; json:&amp;#34;artist&amp;#34; gorm:&amp;#34;index&amp;#34;`&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Album&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;`plist:&amp;#34;Album&amp;#34; json:&amp;#34;album&amp;#34; gorm:&amp;#34;index&amp;#34;`&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;TotalTime&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;`plist:&amp;#34;Total Time&amp;#34; json:&amp;#34;total_time&amp;#34;`&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Location&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;`plist:&amp;#34;Location&amp;#34; json:&amp;#34;location&amp;#34;`&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Rating&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;`plist:&amp;#34;Rating&amp;#34; json:&amp;#34;rating&amp;#34; gorm:&amp;#34;index&amp;#34;`&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;PlayCount&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;`plist:&amp;#34;Play Count&amp;#34; json:&amp;#34;play_count&amp;#34;`&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;DateAdded&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Time&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;`plist:&amp;#34;Date Added&amp;#34; json:&amp;#34;date_added&amp;#34; gorm:&amp;#34;index&amp;#34;`&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// ... 60+ more fields&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;The initial version parsed the XML on every startup using &lt;code&gt;howett.net/plist&lt;/code&gt;. That worked fine for a 15,000-track library (about 2 seconds of parsing). But it eventually moved to SQLite with GORM for persistence, which also enabled FTS5 full-text search and pre-aggregated album caches.&lt;/p&gt;
&lt;h2 id="the-go-backend"&gt;The Go backend&lt;/h2&gt;
&lt;p&gt;The server is straightforward. A &lt;code&gt;LibraryService&lt;/code&gt; holds the database, artwork cache, rate limiter, and various sub-services. Routes are plain &lt;code&gt;net/http&lt;/code&gt; handlers with a JWT auth middleware:&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;figcaption&gt;Route setup (abbreviated)&lt;/figcaption&gt;
&lt;div class="highlight" title="Route setup (abbreviated)"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Auth routes (no middleware)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;HandleFunc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;/auth/login&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;authHandlers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;HandleLogin&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;HandleFunc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;/auth/register&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;authHandlers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;HandleRegister&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Library routes (auth required)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;/tracks&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;authMiddleware&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;HandlerFunc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;HandleTracks&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;/artists&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;authMiddleware&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;HandlerFunc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;HandleArtists&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;/albums&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;authMiddleware&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;HandlerFunc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;HandleAlbums&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;/stream/&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;authMiddleware&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;HandlerFunc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;HandleStream&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;/artwork/track/&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;authMiddleware&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;HandlerFunc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;HandleTrackArtwork&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;The &lt;code&gt;/stream/{id}&lt;/code&gt; endpoint serves audio files directly with &lt;code&gt;http.ServeFile&lt;/code&gt;, which handles range requests and conditional caching automatically. No need to build a custom streaming protocol. HTTP does the job.&lt;/p&gt;
&lt;p&gt;Authentication uses Argon2 for password hashing and session tokens stored in the database. It later grew WebAuthn support for passkey login, and a QR code flow for tvOS (where typing passwords with a remote is painful).&lt;/p&gt;
&lt;h2 id="album-artwork"&gt;Album artwork&lt;/h2&gt;
&lt;p&gt;This was the most annoying part. The exported XML doesn&amp;rsquo;t include artwork. The actual cover images are either embedded in the audio files or live somewhere in Apple&amp;rsquo;s ecosystem that isn&amp;rsquo;t accessible after export.&lt;/p&gt;
&lt;p&gt;The solution is a three-tier artwork system:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Embedded extraction&lt;/strong&gt;: read the audio file metadata with &lt;code&gt;github.com/dhowden/tag&lt;/code&gt;, pull out the cover image if present, cache it locally&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;iTunes Store lookup&lt;/strong&gt;: search Apple&amp;rsquo;s public API for the album, download the highest resolution artwork available&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Manual upload&lt;/strong&gt;: for albums that don&amp;rsquo;t match either source&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The iTunes Store lookup uses a scoring system to match results. Exact album + artist name match scores highest, partial matches score lower, and anything below a minimum threshold is rejected:&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;figcaption&gt;iTunes Store artwork search&lt;/figcaption&gt;
&lt;div class="highlight" title="iTunes Store artwork search"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ls&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;LibraryService&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;searchITunesStore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;artist&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;album&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;tracks&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="nx"&gt;ITunesTrack&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;ITunesSearchItem&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;searchTerm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;%s %s&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;strings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;TrimSpace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;artist&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;strings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;TrimSpace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;album&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Values&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;term&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;searchTerm&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;entity&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;album&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;media&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;music&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;limit&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;10&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;searchURL&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;https://itunes.apple.com/search?%s&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Encode&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// ... fetch, decode, find best match by score&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;Apple&amp;rsquo;s API returns 100x100 thumbnails by default. The URL pattern is predictable (replace &lt;code&gt;100x100&lt;/code&gt; with &lt;code&gt;600x600&lt;/code&gt;), but not all resolutions exist for all albums. The server tries 600, 512, and 300 with HEAD requests before falling back to the original.&lt;/p&gt;
&lt;p&gt;The real constraint is rate limiting. Apple&amp;rsquo;s Search API starts returning 403s if you hit it too hard. The server uses a channel-based rate limiter that caps requests to 2 per second by default, configurable via TOML config.&lt;/p&gt;
&lt;h2 id="the-gapless-playback-problem"&gt;The gapless playback problem&lt;/h2&gt;
&lt;p&gt;This one took a while to figure out. Live albums, DJ mixes, classical recordings: any music that&amp;rsquo;s supposed to flow continuously between tracks gets a gap when played back on iOS.&lt;/p&gt;
&lt;p&gt;The root cause: iOS &lt;code&gt;AVFoundation&lt;/code&gt; ignores the &lt;code&gt;iTunSMPB&lt;/code&gt; gapless metadata in MP3 files. This is the atom that tells the player how many encoder delay and padding samples to skip. Apple&amp;rsquo;s own Music app reads it. &lt;code&gt;AVFoundation&lt;/code&gt; does not.&lt;/p&gt;
&lt;p&gt;The fix is converting MP3s to AAC. The AAC container stores gapless information in a way that &lt;code&gt;AVFoundation&lt;/code&gt; actually respects. The server has a built-in transcoder:&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;figcaption&gt;Startup conversion&lt;/figcaption&gt;
&lt;div class="highlight" title="Startup conversion"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;transcoder&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;tunes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;NewTranscoderService&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;service&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;tunes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;TranscodeOptions&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Concurrency&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;DeleteSource&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// Keep original MP3s&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;results&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;transcoder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ConvertAllMP3s&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Background&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;It uses &lt;code&gt;ffmpeg&lt;/code&gt; under the hood, runs 4 workers in parallel, keeps the originals, and updates the database paths. A 15,000-track library takes a couple of hours. After that, gapless playback works perfectly.&lt;/p&gt;
&lt;h2 id="swiftui-clients"&gt;SwiftUI clients&lt;/h2&gt;
&lt;p&gt;The app targets iOS, macOS, and tvOS from a single codebase, with platform-specific extensions for navigation and layout. iOS uses tab navigation with a mini-player overlay, macOS uses a split view with sidebar, and tvOS uses focus-based navigation for the remote.&lt;/p&gt;
&lt;p&gt;&lt;figure&gt;
&lt;img
src="https://man-you.ringum.net/posts/tunes/tunes-albums-grid_hu_98feb22ba5340b06.webp"
srcset="https://man-you.ringum.net/posts/tunes/tunes-albums-grid_hu_98feb22ba5340b06.webp 960w, https://man-you.ringum.net/posts/tunes/tunes-albums-grid_hu_a0f513b36da95c6f.webp 2622w"
sizes="(max-width: 960px) 100vw, 960px"
alt="Albums grid view on macOS"
width="960"
height="689"
loading="lazy"
decoding="async"
/&gt;
&lt;/figure&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure&gt;
&lt;img
src="https://man-you.ringum.net/posts/tunes/tunes-album-detail_hu_83f6a079ecbe5fc8.webp"
srcset="https://man-you.ringum.net/posts/tunes/tunes-album-detail_hu_83f6a079ecbe5fc8.webp 960w, https://man-you.ringum.net/posts/tunes/tunes-album-detail_hu_b6cec79bf14624e5.webp 2622w"
sizes="(max-width: 960px) 100vw, 960px"
alt="Album detail with track listing"
width="960"
height="689"
loading="lazy"
decoding="async"
/&gt;
&lt;/figure&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure&gt;
&lt;img
src="https://man-you.ringum.net/posts/tunes/tunes-play-queue_hu_eb8ab7a2bbd0b024.webp"
srcset="https://man-you.ringum.net/posts/tunes/tunes-play-queue_hu_eb8ab7a2bbd0b024.webp 960w, https://man-you.ringum.net/posts/tunes/tunes-play-queue_hu_559f2a90737a9dd6.webp 2622w"
sizes="(max-width: 960px) 100vw, 960px"
alt="Play queue popover"
width="960"
height="689"
loading="lazy"
decoding="async"
/&gt;
&lt;/figure&gt;&lt;/p&gt;
&lt;p&gt;The audio player is built on &lt;code&gt;AVQueuePlayer&lt;/code&gt; for seamless track transitions. It pre-buffers the next track 10 seconds before the current one ends, maintaining up to 3 items in the queue at any time.&lt;/p&gt;
&lt;p&gt;One hard-won lesson: &lt;code&gt;@Published&lt;/code&gt; property updates in background cause iOS to terminate the app for excessive main-thread UI work. The fix is gating all published writes behind a foreground check:&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;figcaption&gt;Background-safe state management&lt;/figcaption&gt;
&lt;div class="highlight" title="Background-safe state management"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-swift" data-lang="swift"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="nc"&gt;PlaybackState&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Equatable&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;currentTime&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;TimeInterval&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;TimeInterval&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;isPlaying&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;isLoading&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lhs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;PlaybackState&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rhs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;PlaybackState&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Bool&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lhs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;currentTime&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="nb"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rhs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;currentTime&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lhs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="nb"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rhs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;lhs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isPlaying&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="n"&gt;rhs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isPlaying&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;lhs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isLoading&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="n"&gt;rhs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isLoading&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;Grouping related properties into structs (instead of individual &lt;code&gt;@Published&lt;/code&gt; vars) and implementing coarse equality comparison reduced SwiftUI re-renders significantly. The player only triggers a view update when the &lt;em&gt;second&lt;/em&gt; changes, not on every time observer tick.&lt;/p&gt;
&lt;p&gt;The API layer uses Combine publishers with automatic 401 retry: if a request fails with an expired token, the service transparently refreshes it and replays the original request. Network monitoring via &lt;code&gt;NWPathMonitor&lt;/code&gt; lets the app degrade gracefully when offline, falling back to Core Data for cached data.&lt;/p&gt;
&lt;h2 id="what-grew-out-of-it"&gt;What grew out of it&lt;/h2&gt;
&lt;p&gt;What started as &amp;ldquo;stream my library from a Raspberry Pi&amp;rdquo; has accumulated features over time:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Full-text search&lt;/strong&gt; with SQLite FTS5 across all track metadata&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Play history tracking&lt;/strong&gt;: every play gets logged with duration and completion status, which feeds a yearly replay feature (like Spotify Wrapped)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Lyrics&lt;/strong&gt;: embedded USLT frames from audio files, plus LRClib API integration for synced timestamps&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Offline mode&lt;/strong&gt;: Core Data persistence with background sync, download management for keeping albums locally&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Discogs integration&lt;/strong&gt;: artist biographies and metadata from a separate database&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Vector similarity search&lt;/strong&gt;: track embeddings for &amp;ldquo;genius playlists&amp;rdquo; based on audio features and metadata&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It&amp;rsquo;s not a replacement for Apple Music. It doesn&amp;rsquo;t have a catalog of 100 million songs. But it plays &lt;em&gt;my&lt;/em&gt; music, with &lt;em&gt;my&lt;/em&gt; metadata, on all my devices, without a subscription. That was the goal.&lt;/p&gt;</description></item></channel></rss>