<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>U-tour — Notes from the pavement</title>
    <link>https://utourapp.netlify.app/blog/</link>
    <atom:link href="https://utourapp.netlify.app/blog/feed.xml" rel="self" type="application/rss+xml"/>
    <description>Writing from the team building U-tour: how a running tour guide reads direction, paces its stories, and decides what to say next.</description>
    <language>en</language>
    <lastBuildDate>Sat, 05 Sep 2026 12:00:00 GMT</lastBuildDate>
    <item>
      <title>Why a running tour guide can't trust the compass</title>
      <link>https://utourapp.netlify.app/blog/course-not-compass/</link>
      <guid isPermaLink="true">https://utourapp.netlify.app/blog/course-not-compass/</guid>
      <pubDate>Sat, 05 Sep 2026 12:00:00 GMT</pubDate>
      <description>A phone in a pocket knows which way the phone is facing. It does not know which way you are going. Here is how U-tour works out direction, and why the compass is the fallback rather than the source.</description>
      <category>engineering</category>
      <category>direction</category>
      <content:encoded><![CDATA[<p>Every audio guide has to answer one question before it says a word: which way is the listener facing? Get it wrong and the app narrates a church you passed a minute ago, or tells you the fountain is on your left when it is on your right. Nothing else about the experience survives that.</p>
<p>For a walking-tour app the answer is usually the magnetometer. The phone is in your hand, held up like a map, and the compass reading is the direction you are looking. For a <em>running</em> app that assumption breaks on the first stride. The phone is in a pocket, an armband, or a running belt. The compass faithfully reports which way the phone is pointing, which is some angle off your hip, and that angle changes with every step.</p>
<h2 id="two-sources-opposite-failure-modes">Two sources, opposite failure modes</h2>
<p>There are two signals a phone can give you, and they fail in opposite conditions.</p>
<ul><li><strong>Course over ground.</strong> The GPS tells you the direction of travel by comparing successive fixes. It is honest about where your body is going regardless of how the phone is held. It falls apart when you are slow: below walking pace, GPS jitter is bigger than the distance you moved, and the &quot;course&quot; is noise.</li><li><strong>The magnetometer.</strong> Reliable when you are standing still, since it does not need you to move. Useless in a pocket, because it reads the phone, not the traveller.</li></ul>
<p>Course over ground is the primary signal in U-tour. The compass is what the app reaches for only when you are too slow for course to mean anything. That is the reverse of how most guides are built, and it is the single decision the rest of the direction stack rests on.</p>
<h2 id="what-happens-at-a-red-light">What happens at a red light</h2>
<p>Reversing the priority creates a new problem. You are running east along the Embankment, you stop at a crossing, and your speed drops to zero. Course over ground has nothing to say. The compass says the phone is pointing roughly north, because that is how it sits in your pocket. A naive engine would swap to the compass and, for the thirty seconds you stand there, believe you are facing north. Anything it queued up in that window would be wrong.</p>
<p>U-tour keeps two ideas separate. There is the <em>heading estimate</em>, which is the best guess right now and is allowed to say &quot;stationary, compass says north-ish&quot;. And there is the <em>direction of travel</em>, which is only ever set from course fixes and is held through a stop. The held direction stays east at the red light, so the landmarks queued ahead of you stay the landmarks ahead of you.</p>
<p>The hold is not a timer. It is argued down by evidence: a couple of course fixes that contradict it, once you are moving again, and it turns with you. A hold that expires on a clock would either drop too early at a long light or cling too long after you turned a corner.</p>
<h2 id="not-claiming-what-it-doesn-t-know">Not claiming what it doesn't know</h2>
<p>The last piece is being honest about confidence. When the heading is shaky, U-tour widens the cone it looks in and drops the directional language. A landmark is &quot;nearby&quot; rather than &quot;on your right&quot;. That reads as a small loss of polish, and it is far better than confidently sending someone the wrong way.</p>
<p>The cone widens to eighty-five degrees either side, and deliberately not past ninety. Beyond a right angle a landmark is behind your shoulder, and you would have to turn round to see it. A wider cone would not be generosity. It would be breaking the promise that the guide talks about what is in front of you.</p>
<h2 id="why-it-is-testable-at-all">Why it is testable at all</h2>
<p>All of this math lives in a module with no CoreLocation import. It takes plain coordinates, speeds and timestamps, which means a whole run can be replayed on the command line against a synthetic track with no phone, no simulator and no Xcode. Direction is the part of the product that everything else depends on, so it is the part we wanted to be able to prove rather than eyeball.</p>]]></content:encoded>
    </item>
    <item>
      <title>How far ahead should a guide look?</title>
      <link>https://utourapp.netlify.app/blog/lead-time-not-radius/</link>
      <guid isPermaLink="true">https://utourapp.netlify.app/blog/lead-time-not-radius/</guid>
      <pubDate>Sat, 05 Sep 2026 12:00:00 GMT</pubDate>
      <description>A fixed trigger radius is tuned for one speed and wrong at every other. U-tour measures the lookahead in seconds instead, so the story finishes as the landmark arrives whether you are walking or sprinting.</description>
      <category>engineering</category>
      <category>pacing</category>
      <content:encoded><![CDATA[<p>The obvious way to trigger narration is a circle. When the listener is within, say, one hundred metres of a landmark, start talking. Every location app has a number like this somewhere, and it works fine as long as everyone moves at the speed the number was tuned for.</p>
<p>Runners don't. At a walking pace of 1.4 metres per second, a hundred metres is over a minute of warning, and the story about the abbey ends long before you reach it. At three metres per second it is thirty seconds, which for a two-paragraph story means you are past the building before the second paragraph starts. Tune the number for runners and walkers get talked at about things they can't see yet.</p>
<h2 id="measure-in-seconds-not-metres">Measure in seconds, not metres</h2>
<p>U-tour's trigger doesn't hold a distance. It holds a <em>lead time</em>: twenty seconds of travel. The radius is derived from that and your current speed, so a walker gets about thirty metres of reach and a runner gets sixty, and in both cases the narration starts roughly twenty seconds before the landmark. The same rule stretches to a bike or a boat without a per-activity table of magic numbers.</p>
<p>Two clamps keep it sane. A floor of eighty metres means the app still works when you are barely moving, browsing a square on foot. A ceiling of twelve hundred metres stops a fast vehicle from pulling in the whole coastline at once.</p>
<h2 id="the-cone">The cone</h2>
<p>Reach is only half the trigger. The other half is angle. U-tour looks in a cone sixty degrees either side of your direction of travel. Inside twenty-five degrees a landmark is &quot;coming up ahead&quot;; outside that it is on your left or right. Anything outside the cone is not a candidate, however close.</p>
<p>That last rule is the one that matters most. Being told about a building you passed a minute ago is the failure that makes an app feel broken, and it is easy to produce with a plain radius, because a circle has no front. The cone gives it one.</p>
<h2 id="not-a-firehose">Not a firehose</h2>
<p>In a dense city centre the trigger would fire constantly if it could. A minimum gap of ninety seconds between cues keeps a block of five listed buildings from becoming a wall of speech. The queue holds the rest, ordered nearest first, and takes the next one when the cooldown clears. Some landmarks go unheard in a dense area. That is the intended trade: the alternative is hearing all of them badly.</p>
<h2 id="what-we-didn-t-do">What we didn't do</h2>
<p>An earlier version had the trigger reach further for &quot;important&quot; landmarks, scaling the radius by a significance rating. It felt sensible and produced a complaint we came to know well: being told about something further away than the thing you were standing next to. The ranking side of that story is its own post. On the trigger side, the lesson was that distance and time are facts about the listener, and significance is an opinion about the place. The trigger runs on facts.</p>]]></content:encoded>
    </item>
    <item>
      <title>Nearest first: the four ranking rules we deleted</title>
      <link>https://utourapp.netlify.app/blog/nearest-first/</link>
      <guid isPermaLink="true">https://utourapp.netlify.app/blog/nearest-first/</guid>
      <pubDate>Sat, 05 Sep 2026 12:00:00 GMT</pubDate>
      <description>Over the life of U-tour the landmark queue picked up four clever ways to choose what to narrate next. Each produced the same complaint. The queue is now ordered by distance and not much else.</description>
      <category>engineering</category>
      <category>design</category>
      <content:encoded><![CDATA[<p>When several landmarks are inside the trigger cone at once, something has to decide which one gets narrated. This is the part of a tour guide that feels like it should be smart. It was, four separate times, and every version was worse than the dumb one.</p>
<p>The complaint was identical each time, and it came from the same place: walking along, being told about something further away than the thing you were standing next to. From the pavement that is indistinguishable from the app picking at random. Here are the four rules, in the order we removed them.</p>
<h2 id="1-ahead-beats-everything">1. Ahead beats everything</h2>
<p>The first version split the queue into &quot;inside the forward cone&quot; and &quot;outside it&quot;, and ranked everything in the first group above everything in the second. It sounds right. In practice a landmark five hundred metres straight ahead beat one twenty metres to your left, because the near one was a few degrees outside the cone. You walked past the near thing in silence while the app warmed up a story about something you couldn't see yet.</p>
<h2 id="2-the-bearing-penalty">2. The bearing penalty</h2>
<p>Version two was gentler. Instead of a hard split it inflated the cost of anything off to the side, by up to a third at ninety degrees. Same complaint, smaller magnitude. A landmark beside you still lost to one further off that happened to be more in line with your stride.</p>
<h2 id="3-prefer-the-recorded-voice">3. Prefer the recorded voice</h2>
<p>U-tour narrates with a server-side recording when one has been synthesised and falls back to the on-device voice when it hasn't. Switching voices mid-tour is jarring, so version three penalised landmarks whose recording wasn't ready yet, by a factor of two and a half. That let a voiced landmark at two hundred metres beat an unvoiced one at ninety. The listener was walked past the near thing to preserve a voice.</p>
<h2 id="4-significance">4. Significance</h2>
<p>The most defensible of the four, and still wrong. Each landmark carries a rating of how significant it is, and the queue let a landmark rated five win from two and a half times further out. The problem is what the rating <em>is</em>: a judgement about the place, made by a model that has never seen the street. Distance is a fact about the listener. Weighing the two against each other lost roughly one cue in five to something across a junction.</p>
<h2 id="what-s-left">What's left</h2>
<p>Distance. The cone still decides <em>whether</em> it is time to say something. It no longer decides <em>which</em>. Nearest first, and that is the whole rule.</p>
<p>The cost is worth stating plainly. The queue will sometimes narrate a landmark you have just walked past, and it falls back to the on-device voice more often, because the nearest thing is the one least likely to have finished synthesising. Both were judged better than the alternative, which was the app forming its own opinion about what you should hear and overriding your position with it.</p>
<h2 id="the-exceptions-and-why-they-aren-t-a-fifth-rule">The exceptions, and why they aren't a fifth rule</h2>
<p>Two things do bend the order, and the reason they survive is that they are a different <em>shape</em> from the four above.</p>
<p>The first is a pin. If you tap a landmark in the queue browser while looking at its photograph and say &quot;that one&quot;, it jumps the queue, from within a bounded reach and, when the heading is trustworthy, from in front. That is not the app's opinion. It is yours, and honouring it is not second-guessing anyone.</p>
<p>The second is a narrow tie-breaker. Every removed rule was unbounded: a multiplier or a partition that could pull a landmark past any number of nearer ones. The tie-breakers can only reorder landmarks that are already about the same distance away, measured from whatever is nearest right now. On a quiet street that band holds one candidate and the tie-breakers do nothing. In a city centre it holds several, and choosing between things you can all see at once is a different act from walking you past the near one to reach a favoured far one.</p>
<p>The property being bought is that the rule limits itself by density. Where it could go wrong, it is inert. Where it does something, everything it chooses between is already in front of you.</p>]]></content:encoded>
    </item>
  </channel>
</rss>
