<?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>Ken Reid's Blog</title>
    <link>https://www.kenreid.co.uk/blog.html</link>
    <description>Data science, photography, books, and everything in between.</description>
    <language>en</language>
    <lastBuildDate>Mon, 05 Oct 2026 00:00:00 +0000</lastBuildDate>
    <pubDate>Mon, 05 Oct 2026 00:00:00 +0000</pubDate>
    <docs>https://www.rssboard.org/rss-specification</docs>
    <generator>Ken Reid static site feed</generator>
    <atom:link href="https://www.kenreid.co.uk/feed.xml" rel="self" type="application/rss+xml"/>
    <item>
      <title>Every Stephen King Book I&#x27;ve Read, Ranked</title>
      <link>https://www.kenreid.co.uk/blog/stephen-king-ranked.html</link>
      <guid>https://www.kenreid.co.uk/blog/stephen-king-ranked.html</guid>
      <pubDate>Mon, 05 Oct 2026 00:00:00 +0000</pubDate>
      <description>Every Stephen King book I&#x27;ve read, ranked: ten books from The Shining to It, by someone who rates King below his own average but keeps going back.</description>
      <category>books</category>
      <content:encoded><![CDATA[

 <p>I've read ten Stephen King books, and they average 3.6 stars from me. My whole shelf averages 4.0, so at least on average King is a below-average author by my standards, which is a ridiculous thing to say about Stephen King. And yet I've kept going back. So here I'm going to dig into why, a little: what about his multiverse pulls me in, and what makes me rank the books low in the end.</p>

 <p>Ten books is nothing next to his bibliography, and four I've read are short stories or novellas, so this is not the definitive ranking by any means. The Goodreads average is next to each one for comparison. Lowest first.</p>

  <figure class="kr-figure--solo kr-dotplot">
   <p class="kr-dotplot__legend" aria-hidden="true"><span class="kr-dotplot__key">My rating</span><span class="kr-dotplot__key kr-dotplot__key--crowd">Goodreads average</span></p>
   <ol class="kr-dotplot__rows">
    <li style="--kr-dot-me: 5; --kr-dot-crowd: 4.28" data-rank="1" data-year="1977"><span class="kr-dotplot__label"><em>The Shining</em><span class="sr-only"> (1977), ranked 1st: me 5, Goodreads 4.28</span></span><span class="kr-dotplot__track" aria-hidden="true"><span class="kr-dotplot__gap"></span><span class="kr-dotplot__dot kr-dotplot__dot--crowd"></span><span class="kr-dotplot__dot"></span></span></li>
    <li style="--kr-dot-me: 4; --kr-dot-crowd: 4.21" data-rank="2" data-year="1983"><span class="kr-dotplot__label"><em>Pet Sematary</em><span class="sr-only"> (1983), ranked 2nd: me 4, Goodreads 4.21</span></span><span class="kr-dotplot__track" aria-hidden="true"><span class="kr-dotplot__gap"></span><span class="kr-dotplot__dot kr-dotplot__dot--crowd"></span><span class="kr-dotplot__dot"></span></span></li>
    <li style="--kr-dot-me: 4; --kr-dot-crowd: 3.94" data-rank="3" data-year="1982"><span class="kr-dotplot__label"><em>The Gunslinger</em><span class="sr-only"> (1982), ranked 3rd: me 4, Goodreads 3.94</span></span><span class="kr-dotplot__track" aria-hidden="true"><span class="kr-dotplot__gap"></span><span class="kr-dotplot__dot kr-dotplot__dot--crowd"></span><span class="kr-dotplot__dot"></span></span></li>
    <li style="--kr-dot-me: 4; --kr-dot-crowd: 3.92" data-rank="4" data-year="1982"><span class="kr-dotplot__label"><em>Apt Pupil</em><span class="sr-only"> (1982), ranked 4th: me 4, Goodreads 3.92</span></span><span class="kr-dotplot__track" aria-hidden="true"><span class="kr-dotplot__gap"></span><span class="kr-dotplot__dot kr-dotplot__dot--crowd"></span><span class="kr-dotplot__dot"></span></span></li>
    <li style="--kr-dot-me: 4; --kr-dot-crowd: 3.80" data-rank="5" data-year="2005"><span class="kr-dotplot__label"><em>Things They Left Behind</em><span class="sr-only"> (2005), ranked 5th: me 4, Goodreads 3.80</span></span><span class="kr-dotplot__track" aria-hidden="true"><span class="kr-dotplot__gap"></span><span class="kr-dotplot__dot kr-dotplot__dot--crowd"></span><span class="kr-dotplot__dot"></span></span></li>
    <li style="--kr-dot-me: 4; --kr-dot-crowd: 4.16" data-rank="6" data-year="1981"><span class="kr-dotplot__label"><em>The Jaunt</em><span class="sr-only"> (1981), ranked 6th: me 4, Goodreads 4.16</span></span><span class="kr-dotplot__track" aria-hidden="true"><span class="kr-dotplot__gap"></span><span class="kr-dotplot__dot kr-dotplot__dot--crowd"></span><span class="kr-dotplot__dot"></span></span></li>
    <li style="--kr-dot-me: 3; --kr-dot-crowd: 3.60" data-rank="7" data-year="1993"><span class="kr-dotplot__label"><em>The House on Maple Street</em><span class="sr-only"> (1993), ranked 7th: me 3, Goodreads 3.60</span></span><span class="kr-dotplot__track" aria-hidden="true"><span class="kr-dotplot__gap"></span><span class="kr-dotplot__dot kr-dotplot__dot--crowd"></span><span class="kr-dotplot__dot"></span></span></li>
    <li style="--kr-dot-me: 3; --kr-dot-crowd: 4.00" data-rank="8" data-year="1974"><span class="kr-dotplot__label"><em>Carrie</em><span class="sr-only"> (1974), ranked 8th: me 3, Goodreads 4.00</span></span><span class="kr-dotplot__track" aria-hidden="true"><span class="kr-dotplot__gap"></span><span class="kr-dotplot__dot kr-dotplot__dot--crowd"></span><span class="kr-dotplot__dot"></span></span></li>
    <li style="--kr-dot-me: 3; --kr-dot-crowd: 3.80" data-rank="9" data-year="1981"><span class="kr-dotplot__label"><em>Cujo</em><span class="sr-only"> (1981), ranked 9th: me 3, Goodreads 3.80</span></span><span class="kr-dotplot__track" aria-hidden="true"><span class="kr-dotplot__gap"></span><span class="kr-dotplot__dot kr-dotplot__dot--crowd"></span><span class="kr-dotplot__dot"></span></span></li>
    <li style="--kr-dot-me: 2; --kr-dot-crowd: 4.27" data-rank="10" data-year="1986"><span class="kr-dotplot__label"><em>It</em><span class="sr-only"> (1986), ranked 10th: me 2, Goodreads 4.27</span></span><span class="kr-dotplot__track" aria-hidden="true"><span class="kr-dotplot__gap"></span><span class="kr-dotplot__dot kr-dotplot__dot--crowd"></span><span class="kr-dotplot__dot"></span></span></li>
   </ol>
   <p class="kr-dotplot__axis" aria-hidden="true"><span class="kr-dotplot__ticks"><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span></span></p>
   <figcaption class="figure-note">My rating for each book against its Goodreads average, in stars, in the order I ranked them. Hover over a book, or tap it, for the details.</figcaption>
  </figure>

 <p>Goodreads readers and I agree on the best of King's work (in my opinion): <em>The Shining</em> has the highest average of the ten, 4.28. We disagree most about the book in second place on their list. <em>It</em> averages 4.27, and I gave it two stars. Below the top, my ratings mostly sit within half a star of everyone else's, except <em>Carrie</em> and <em>Cujo</em>, which I liked less than most people seem to.</p>

 <div class="bsec">
 <h2>10. <em>It</em> (1986)</h2>
 <div class="kr-book-side">
  <img class="book-cover" src="https://www.kenreid.co.uk/img/covers/9780451169518.webp" alt="It cover" loading="lazy" width="289" height="500">
  <dl class="kr-rating">
   <div><dt>Me</dt><dd><span class="kr-rating__stars" style="--kr-stars: 2" aria-hidden="true"></span>2<span class="sr-only"> out of 5</span></dd></div>
   <div class="kr-rating__crowd"><dt>Goodreads</dt><dd><span class="kr-rating__stars" style="--kr-stars: 4.27" aria-hidden="true"></span>4.27<span class="sr-only"> out of 5</span></dd></div>
  </dl>
 </div>

 <p><em>It</em> has a brilliant and terrifying premise: a group of kids in Derry, Maine, unite to confront an ancient, shapeshifting entity that feeds on fear, then return 27 years later as adults to finish the fight. But, sadly, I had to give it two stars. King's writing is atmospheric but lacking something in immersion. I find the mythology of his wider universe really interesting, but I didn't like this one the way I expected to. The last quarter is so abstract, exploring concepts rather than actual events, even in the fight scenes, that I lost interest pretty quickly. And, of course, the scene with the children in the sewers near the end was gross. I get that it fits the theme, but it didn't sit well with me, and regardless of what it conveys conceptually or King's intent in writing it (or how much his face resembled someone who fell into fresh snow at the time of writing), I tend to find an "orgy of children" to be at best uncomfortable, unnecessary and concerning.</p>

 <p>I wish there was more exploration of It as a creature, the deep dark mythology of the Lovecraftian monster with its odd diet and fingers in various stories - especially considering the epic length of the book. Speaking of the length, there were various subplots and meandering chapters that felt unfinished and/or unnecessary. All in all, a disappointing read.</p>
 </div>

 <div class="bsec">
 <h2>9. <em>Cujo</em> (1981)</h2>
 <div class="kr-book-side kr-book-side--right">
  <img class="book-cover" src="https://www.kenreid.co.uk/img/covers/9780451161352.webp" alt="Cujo cover" loading="lazy" width="294" height="500">
  <dl class="kr-rating">
   <div><dt>Me</dt><dd><span class="kr-rating__stars" style="--kr-stars: 3" aria-hidden="true"></span>3<span class="sr-only"> out of 5</span></dd></div>
   <div class="kr-rating__crowd"><dt>Goodreads</dt><dd><span class="kr-rating__stars" style="--kr-stars: 3.80" aria-hidden="true"></span>3.80<span class="sr-only"> out of 5</span></dd></div>
  </dl>
 </div>

 <p>A mother and her son trapped in a sweltering car by a rabid Saint Bernard is a great setup, and the survival story at the middle of it is interesting, psychological and tragic. The writing around it is sluggish. It's a disjointed stream-of-consciousness style with no chapters, which keeps breaking the flow (chapterless books are fine but you need to make it work!). The tension also keeps getting derailed by bloated subplots about a failing cereal marketing campaign (???) and an affair, so it's hard to stay lost in the horror. A decent, middle-of-the-road horror novel that must have skipped the editor on the way to publication.</p>
 </div>

 <div class="bsec">
 <h2>8. <em>Carrie</em> (1974)</h2>
 <div class="kr-book-side">
  <img class="book-cover" src="https://www.kenreid.co.uk/img/covers/9780385086950.webp" alt="Carrie cover" loading="lazy" width="307" height="475">
  <dl class="kr-rating">
   <div><dt>Me</dt><dd><span class="kr-rating__stars" style="--kr-stars: 3" aria-hidden="true"></span>3<span class="sr-only"> out of 5</span></dd></div>
   <div class="kr-rating__crowd"><dt>Goodreads</dt><dd><span class="kr-rating__stars" style="--kr-stars: 4.00" aria-hidden="true"></span>4.00<span class="sr-only"> out of 5</span></dd></div>
  </dl>
 </div>

 <p><em>Carrie</em> was King's first novel and it carries a lot of cultural weight (though I think the movie did a lot of that with the visuals), but it reads like a first book: raw, messy and sometimes brilliant. The core of the story is a page-turner for sure. King nails the cruelty of high school and what it's like to live with an abusive, fanatical parent, and you feel for Carrie White so much that the prom, when it comes, is tragic and also super cathartic.</p>

 <p>The execution is clunky. The story keeps cutting away to fake news clippings, book excerpts and reports, which kills the suspense right when it builds, at least it did for me. Margaret White and Chris Hargensen are cartoonishly evil, there to push Carrie over the edge and not much else. It's absolutely worth reading to see where the master of horror started.</p>

 <p>Mike Flanagan's <em>Carrie</em> series comes to Prime Video on 7 October 2026, all eight episodes at once, with the story moved into the social-media age. As a huge fan of the Flaniverse, I know I'll be watching this.</p>
 </div>

 <div class="bsec">
 <h2>7. <em>The House on Maple Street</em> (1993)</h2>
 <div class="kr-book-side kr-book-side--right">
  <img class="book-cover" src="https://www.kenreid.co.uk/img/covers/9780743598217.webp" alt="The House on Maple Street and Other Stories cover" loading="lazy" width="432" height="500">
  <dl class="kr-rating">
   <div><dt>Me</dt><dd><span class="kr-rating__stars" style="--kr-stars: 3" aria-hidden="true"></span>3<span class="sr-only"> out of 5</span></dd></div>
   <div class="kr-rating__crowd"><dt>Goodreads</dt><dd><span class="kr-rating__stars" style="--kr-stars: 3.60" aria-hidden="true"></span>3.60<span class="sr-only"> out of 5</span></dd></div>
  </dl>
 </div>

 <p>The hook is fun: the Bradbury kids work out that their house is swapping its wood and plaster for alien metal and a countdown clock, and they use it to get rid of their awful stepfather by trapping him inside just before liftoff. It's a creative nod to classic sci-fi, it moves fast, and the worry for their mum gives it some weight. But it didn't pull me in. The jump from a family drama to a literal spaceship taking off is too abrupt, and nobody ever explains how the house works, so it reads like a B-movie or a pulp comic, not a world I could sink into.</p>
 </div>

 <div class="bsec">
 <h2>6. <em>The Jaunt</em> (1981)</h2>
 <div class="kr-book-side">
  <img class="book-cover" src="https://www.kenreid.co.uk/img/covers/olcover-14657166.webp" alt="Skeleton Crew cover, the collection that includes The Jaunt" loading="lazy" width="334" height="500">
  <dl class="kr-rating">
   <div><dt>Me</dt><dd><span class="kr-rating__stars" style="--kr-stars: 4" aria-hidden="true"></span>4<span class="sr-only"> out of 5</span></dd></div>
   <div class="kr-rating__crowd"><dt>Goodreads</dt><dd><span class="kr-rating__stars" style="--kr-stars: 4.16" aria-hidden="true"></span>4.16<span class="sr-only"> out of 5</span></dd></div>
  </dl>
 </div>

 <p>A short story about teleportation, and it tickles a certain trope I adore: the Jaunt isn't instant travel from A to B, it's something darker. In King's future everyone jaunts, but you have to be put under first, because anyone who goes through awake spends an eternity alone in white nothing while their body arrives in an instant. The dread lands on you the same way it lands on the characters.</p>

 <p>The father spends a big chunk of the story telling his kids the history of the technology, and the lore is interesting, and if the story were longer it would have a purpose, but as it is the tension of the family's trip just stops for a history lesson. Once the jump happens it drags you straight back in, and the ending leaves with an unsettling idea to consider.</p>
 </div>

 <div class="bsec">
 <h2>5. <em>Things They Left Behind</em> (2005)</h2>
 <div class="kr-book-side kr-book-side--right">
  <img class="book-cover" src="https://www.kenreid.co.uk/img/covers/olid-OL48265684M.webp" alt="Just After Sunset cover, the collection that includes Things They Left Behind" loading="lazy" width="253" height="500">
  <dl class="kr-rating">
   <div><dt>Me</dt><dd><span class="kr-rating__stars" style="--kr-stars: 4" aria-hidden="true"></span>4<span class="sr-only"> out of 5</span></dd></div>
   <div class="kr-rating__crowd"><dt>Goodreads</dt><dd><span class="kr-rating__stars" style="--kr-stars: 3.80" aria-hidden="true"></span>3.80<span class="sr-only"> out of 5</span></dd></div>
  </dl>
 </div>

 <p>A short story about survivor's guilt after 9/11. Scott Staley called in sick to his job at the World Trade Center that morning, and a year later things that belonged to his dead coworkers start turning up in his apartment, a pair of sunglasses, a baseball bat. King keeps the supernatural part completely ordinary and it works: the guilt is heavy and Scott can't get out from under it. It loses a star because once he works out he has to take the things back to the families, it turns into the same visit over and over. Tying up a real tragedy with a neat supernatural bow also gets a bit too sentimental for me, and it undoes some of the realism of the first half. Still a great read.</p>
 </div>

 <div class="bsec">
 <h2>4. <em>Apt Pupil</em> (1982)</h2>
 <div class="kr-book-side">
  <img class="book-cover" src="https://www.kenreid.co.uk/img/covers/9780751525670.webp" alt="Apt Pupil cover" loading="lazy" width="293" height="500">
  <dl class="kr-rating">
   <div><dt>Me</dt><dd><span class="kr-rating__stars" style="--kr-stars: 4" aria-hidden="true"></span>4<span class="sr-only"> out of 5</span></dd></div>
   <div class="kr-rating__crowd"><dt>Goodreads</dt><dd><span class="kr-rating__stars" style="--kr-stars: 3.92" aria-hidden="true"></span>3.92<span class="sr-only"> out of 5</span></dd></div>
  </dl>
 </div>

 <p>This one is disturbing, and it's about people, not the supernatural. Todd Bowden is an American golden boy who finds out his elderly neighbor is Kurt Dussander, a Nazi war criminal in hiding. Instead of turning him in, Todd blackmails him into telling every gruesome detail of what he did. It's a weird parasitic relationship, with the boy manipulating this old man, who appears to just want to live out his days in peace, leaving his awful past behind. At first, at least. The premise is great and you keep hoping the 'apt pupil' will wake up and be redeemed.</p>

 <p>Then the years pass, both of them start killing local transients on their own, and the tension of those talks in Dussander's kitchen turns into so much violence that it numbs more than it scares. The pacing and execution of the second half is a bit off. The ending is bleak and explosive, and there isn't one sympathetic character in the whole thing.</p>
 </div>

 <div class="bsec">
 <h2>3. <em>The Gunslinger</em> (1982)</h2>
 <div class="kr-book-side kr-book-side--right">
  <img class="book-cover" src="https://www.kenreid.co.uk/img/covers/olcover-8396638.webp" alt="The Gunslinger cover" loading="lazy" width="337" height="500">
  <dl class="kr-rating">
   <div><dt>Me</dt><dd><span class="kr-rating__stars" style="--kr-stars: 4" aria-hidden="true"></span>4<span class="sr-only"> out of 5</span></dd></div>
   <div class="kr-rating__crowd"><dt>Goodreads</dt><dd><span class="kr-rating__stars" style="--kr-stars: 3.94" aria-hidden="true"></span>3.94<span class="sr-only"> out of 5</span></dd></div>
  </dl>
 </div>

 <p>I was surprised to learn as a teenager that Stephen King had written an epic fantasy series, as I had only known him for horror. <em>The Gunslinger</em> is the first Dark Tower book: Roland Deschain chasing the Man in Black across a dying desert, and the heat and the emptiness pull you right in. The world is completely bizarre, high fantasy and post-apocalyptic sci-fi and a brutal spaghetti western, of all things, and King doesn't explain any of it, he just drops you in. It was first published as separate short stories and you can tell. Roland's past in Gilead is fascinating, but the flashbacks keep stopping the chase. The final act is a huge, mind-bending philosophical conversation that leaves you with far more questions than answers - this isn't bad but it does make the pacing a bit disjointed.</p>
 </div>

 <div class="bsec">
 <h2>2. <em>Pet Sematary</em> (1983)</h2>
 <div class="kr-book-side">
  <img class="book-cover" src="https://www.kenreid.co.uk/img/covers/olcover-12015500.webp" alt="Pet Sematary cover" loading="lazy" width="320" height="500">
  <dl class="kr-rating">
   <div><dt>Me</dt><dd><span class="kr-rating__stars" style="--kr-stars: 4" aria-hidden="true"></span>4<span class="sr-only"> out of 5</span></dd></div>
   <div class="kr-rating__crowd"><dt>Goodreads</dt><dd><span class="kr-rating__stars" style="--kr-stars: 4.21" aria-hidden="true"></span>4.21<span class="sr-only"> out of 5</span></dd></div>
  </dl>
 </div>

 <p>I first read <em>Pet Sematary</em> as a kid, and it left a creepy impression on me. It's about how far a parent would go to undo a loss, and King puts you inside Louis Creed's head as he talks himself into each terrible decision on the way to the old burial ground. The walk through the woods past the deadfall is the scariest part of the book, and it makes the supernatural feel real. The slow part is the build-up, with lots of the Creeds' everyday family life and lots of foreshadowing. It makes the loss hurt more when it comes, but the story stalls before the third act.</p>

 <p>The film adaptations don't live up to the writing either. They go for shock and gore and miss the tension. The real horror isn't the cat or the toddler, it's grief, and how much pain it takes before you'd do anything to make it stop.</p>
</div>

 <div class="bsec">
 <h2>1. <em>The Shining</em> (1977)</h2>
 <div class="kr-book-side kr-book-side--right">
  <img class="book-cover" src="https://www.kenreid.co.uk/img/covers/9780307743657.webp" alt="The Shining cover" loading="lazy" width="294" height="500">
  <dl class="kr-rating">
   <div><dt>Me</dt><dd><span class="kr-rating__stars" style="--kr-stars: 5" aria-hidden="true"></span>5<span class="sr-only"> out of 5</span></dd></div>
   <div class="kr-rating__crowd"><dt>Goodreads</dt><dd><span class="kr-rating__stars" style="--kr-stars: 4.28" aria-hidden="true"></span>4.28<span class="sr-only"> out of 5</span></dd></div>
  </dl>
 </div>

 <p>Five stars, the only one. Jack Torrance takes a winter job as caretaker of the Overlook Hotel, up in the Colorado Rockies, and moves his wife and son in with him. The snow cuts them off, and you watch Jack come apart slowly while the boiler runs too hot and the hotel works on him. The haunting is scary because it's tied to real things, his drinking and the way he's hurt his family before. Unlike in <em>It</em>, King actually gives a good amount of space for the supernatural world to be explored and probed, which gives breadth and depth to his multiverse and the magic within it.</p>

 <p>Kubrick's film is a perfect piece of cinema in its own right, but it's a different story. His Jack looks halfway to madness from the very first frame. King's Jack is a loving but badly flawed father who gets eaten away by the hotel, and that makes the book a tragedy, with a warmth and heartbreak the film leaves out on purpose. In my opinion it's the best example of "the building is the monster" I've ever read.</p>
 </div>

 <figure class="kr-figure--solo">
  <img class="kr-figure-img" src="https://www.kenreid.co.uk/blog/img/stephen-king/timberline-lodge-1943.webp" alt="Black-and-white photograph of Timberline Lodge half buried in snow below the peak of Mount Hood, with snow-covered fir trees in front." loading="lazy" width="1600" height="1225">
  <figcaption class="figure-note">Timberline Lodge on Mount Hood, Oregon, in 1943. Kubrick used it for the outside of the Overlook in his film. Photograph by George Henderson, U.S. Forest Service, via <a href="https://commons.wikimedia.org/wiki/File:Mt._Hood_and_Timberline_Lodge,_1943.jpg" rel="noopener" target="_blank">Wikimedia Commons</a> (public domain).</figcaption>
 </figure>

 <p>There are many books of King's I haven't read, so I can't comment on everything. I should mention I DNF'd <em>The Stand</em> - I had high hopes as I love post-apocalyptic and fantasy books alike, but it felt like a drag and not worth pushing to the end.</p>
 
 <p>I don't describe myself as a King fan, because I find his writing a real mixed bag, and his movies are also mixed with a tendency to be worse. But he is a cultural icon, with the respect of many, and he deserves it for sure. I feel there is an element of Americana in his literature I don't connect with, and I think there's also a generational gap - he writes for a world where kids grew up in the pre-90s, and expects readers to fill in the gaps of the world he draws only an outline for. </p>

 <p>Finally, I find his work lacking in depth. There is such potential in the multiverse, but it's so barely explored and only hinted at, though I know I am missing context in much of the Dark Tower series and potentially other books, too.</p>

 <div class="faq-section">
 <h2>Common questions</h2>

 <details class="faq-item">
 <summary>Which Stephen King book should I start with?</summary>
 <p><em>The Shining</em>.</p>
 </details>

 <details class="faq-item">
 <summary>Why is <em>It</em> last?</summary>
 <p>Because the last quarter lost me, and one scene near the end crossed a line for me that the theme didn't justify.</p>
 </details>
 </div>

 <p class="figure-note">Hero image: Stephen King's house on Broadway in Bangor, Maine, with its bat-and-spiderweb iron gate. Photograph by Warren LeMay, darkened for this page, via <a href="https://commons.wikimedia.org/wiki/File:Stephen_King_House,_Broadway,_Whitney_Park,_Bangor,_ME_(54367077281).jpg" rel="noopener" target="_blank">Wikimedia Commons</a> (<a href="https://creativecommons.org/licenses/by-sa/2.0/" rel="noopener" target="_blank">CC BY-SA 2.0</a>).</p>

        

 ]]></content:encoded>
    </item>
    <item>
      <title>Sleep Science</title>
      <link>https://www.kenreid.co.uk/blog/sleep-science.html</link>
      <guid>https://www.kenreid.co.uk/blog/sleep-science.html</guid>
      <pubDate>Sun, 27 Sep 2026 00:00:00 +0000</pubDate>
      <description>Sleep matters, and the sleep-influencer economy inflates certainty far past the evidence. What&#x27;s solid, what&#x27;s oversold, and why tracking sleep can ruin it.</description>
      <category>science</category>
      <category>advice</category>
      <content:encoded><![CDATA[

<p>I've always had a strange relationship with sleep. I used to experience a lot of insomnia due to my thanatophobia, but nowadays (largely due to changes in life circumstances and being much happier) I tend to sleep soundly. However, I made the (mistake?) decision to read "Why We Sleep: Unlocking the Power of Sleep and Dreams" by Matthew Walker. Now, I have something of an obsession with making sure I have as good a sleep routine as possible.</p>

 <p>In a 2024 paper, Windred and colleagues took more than 10 million hours of accelerometer data from 60,977 people in the UK Biobank and found that how regular someone's sleep was predicted their risk of dying over the following years better than how long they slept<sup><a href="https://www.kenreid.co.uk/blog/sleep-science.html#ref-1" class="cite-ref">[1]</a></sup>. People are obsessed with sleeping pills, mattresses and spending insane amounts of money and time on these, when there are many much simpler (and healthier) solutions backed by actual science - and you can tell it's not some grift because no one is making money off of it (unlike the pills and mattresses).</p>

 <p>I have the same problem with the 'sleep influencers' on YouTube shorts, Instagram reels and TikToks that I have with <a href="https://www.kenreid.co.uk/blog/self-help-books.html">most self-help books</a>. Their whole business is telling you that you're doing sleep wrong and dying faster because of it, and some of that is built on good science, but is driven by a will to make money off of you, through selling books, sleep accessories, diet plans or 1 on 1 mentoring for some obscene costs.</p>

 <div class="plain-english-box">
 <h2>Quick jargon guide</h2>
 <ul>
 <li><strong>Sleep hygiene:</strong> the set of habits and conditions that support sleep: consistent timing, dark, cool, no screens, etc.</li>
 <li><strong>Circadian rhythm:</strong> your body's roughly 24-hour internal clock, set largely by light exposure.</li>
 <li><strong>Sleep architecture:</strong> the structure of a night's sleep in stages (light, deep, REM) cycling through the night.</li>
 <li><strong>Polysomnography:</strong> the overnight sleep study done in a clinic, with sensors on your head and body, that trackers are compared against.</li>
 <li><strong>Orthosomnia:</strong> a coined term for anxiety about achieving "perfect" sleep, often triggered by trackers, that itself wrecks your sleep.</li>
 <li><strong>Sleep apnea:</strong> a serious disorder where breathing repeatedly stops during sleep. Underdiagnosed, and the one thing on this list to actually worry about.</li>
 </ul>
 </div>

 <h2>What actually works</h2>

 <p>The core of the science is solid, and fairly simple. Sleep is when a lot of important biology happens, memory consolidation for one and, going by more recent work, the brain clearing out metabolic waste. Chronic short sleep goes with worse heart health, worse mood, worse thinking and more accidents, and it keeps showing up that way study after study. The thing that really sold "better sleep" to me is that I've been working on my physical health and wellbeing, hitting the gym a lot - and it turns out good sleep is <i>essential</i> to maximize the gains you reap at the gym. What's the point in putting in all the effort to learn how to play guitar, or memorize content for class, or exhaust yourself at the gym, only to throw away a % of the gains from these things by sleeping poorly? So, here is some high level information I found backed by the science:</p>

 <ul>
 <li><strong>Regular times.</strong> Going to bed and waking at the same times every day is one of the best-supported habits there is, and in the Windred study it predicted the risk of dying better than total hours did.</li>
 <li><strong>Light.</strong> Bright light in the morning and dim evenings do more for your body clock than most gadgets.</li>
 <li><strong>Apnea.</strong> If you snore heavily, wake up unrefreshed, or someone has noticed you stop breathing in your sleep, get assessed. Untreated apnea is a real cardiovascular risk, and getting checked for it is covered by most health insurances (from what I've read: I am not your insurance specialist!)</li>
 </ul>

 <figure class="kr-figure--solo">
 <img class="kr-figure-img" src="https://www.kenreid.co.uk/img/photography/thumb/224.webp" alt="A small white heart-shaped tag reading Follow Your Dreams beside a tiny knitted doll, on a dark grey surface" loading="lazy" width="400" height="266" srcset="https://www.kenreid.co.uk/img/photography/thumb/224.webp 400w, https://www.kenreid.co.uk/img/photography/thumb/224@2x.webp 800w, https://www.kenreid.co.uk/img/photography/hero/224.webp 1200w" sizes="(min-width: 800px) 760px, 100vw">
 <figcaption class="figure-note"><span class="figure-copyright">&copy; Ken Reid. All rights reserved.</span></figcaption>
 </figure>

 <h2>Oversold</h2>

 <p>Matthew Walker's <em>Why We Sleep</em> did a lot of good for how seriously people take sleep. It changed my habits more than any other book I've read, and I gave it five stars (which is not particularly rare for me but still). Alexey Guzey went through it and argues that the World Health Organization "sleep loss epidemic" in it doesn't exist (he couldn't find a WHO document declaring one), and that a bar chart on sports injuries, adapted from another study, lost its 5-hour column, the one showing fewer injuries than 6 hours<sup><a href="https://www.kenreid.co.uk/blog/sleep-science.html#ref-2" class="cite-ref">[2]</a></sup>, so some of it is fairly called out, but the central argument very much survives: sleep matters and most of us don't get enough of it.</p>

 <p>Tracker "sleep scores" are largely guesswork. A consumer ring or watch estimates your sleep stages from movement and heart rate. These devices are bad at telling light, deep and REM sleep apart compared with polysomnography, and the app still shows you a confident number every morning. It runs largely guess work and validation.</p>

 <p>Most sleep supplements have little behind them except marketing. Even melatonin, which does have real uses for shifting your body clock, is routinely sold at doses far higher than the evidence supports and taken as a sedative, which it isn't. In fact, the aforementioned book specifies that melatonin is a wonder-drug - for changing your sleep cycle when traveling - only. It's not intended for, nor good at, aiding people with sleep when they're not changing time zones. If you take it otherwise and find it useful, that's great, but it's likely as useful as a sugar pill, but with potential side effects. Think on it and settle for some of the other tips in this page.</p>

 <h2>Orthosomnia</h2>

 <p>Baron and colleagues described <strong>orthosomnia</strong> in 2017: patients so anxious about getting their tracker's numbers right that the anxiety itself was damaging their sleep<sup><a href="https://www.kenreid.co.uk/blog/sleep-science.html#ref-3" class="cite-ref">[3]</a></sup>. You lie awake worrying about whether you're getting enough deep sleep. Or you wake up feeling fine, check the app, find out you "slept badly", and feel worse all day than you would have if you'd never looked.</p>

 <figure class="kr-figure--solo">
 <img class="kr-figure-img" src="https://www.kenreid.co.uk/img/photography/thumb/326.webp" alt="A chipmunk peeking out of the end of a white pipe lying in the grass" loading="lazy" width="400" height="266" srcset="https://www.kenreid.co.uk/img/photography/thumb/326.webp 400w, https://www.kenreid.co.uk/img/photography/thumb/326@2x.webp 800w, https://www.kenreid.co.uk/img/photography/hero/326.webp 1024w" sizes="(min-width: 800px) 760px, 100vw">
 <figcaption class="figure-note"><span class="figure-copyright">&copy; Ken Reid. All rights reserved.</span></figcaption>
 </figure>

 <h2>What to do</h2>

 <p>You don't need to buy anything to sleep well. Roughly in order of how much they help:</p>

 <ol>
 <li><strong>Routine.</strong> Go to bed and get up at the same times every day, weekends included (I know, that's a hard ask). In the Windred study, regularity predicted the risk of dying better than total hours did<sup><a href="https://www.kenreid.co.uk/blog/sleep-science.html#ref-1" class="cite-ref">[1]</a></sup>.</li>
 <li><strong>Light.</strong> Get daylight in the morning, ideally outside, and keep the evenings dim. Light is the main thing that sets your body clock, and a bright screen at midnight tells it it's still daytime.</li>
 <li><strong>Caffeine.</strong> It lasts much longer than it feels like it does. In healthy adults its half-life is between 3 and 7 hours<sup><a href="https://www.kenreid.co.uk/blog/sleep-science.html#ref-5" class="cite-ref">[5]</a></sup>, so half of a 3pm coffee can still be in you at 9pm, and a quarter at bedtime. Drake and colleagues gave people 400mg (two or three big coffees) six hours before bed and it still cut their sleep by more than an hour<sup><a href="https://www.kenreid.co.uk/blog/sleep-science.html#ref-4" class="cite-ref">[4]</a></sup>. If you drink it at all, drink it in the morning (after all, caffeine is actually great for you cognitively and physically<sup><a href="https://www.kenreid.co.uk/blog/sleep-science.html#ref-9" class="cite-ref">[9]</a></sup><sup><a href="https://www.kenreid.co.uk/blog/sleep-science.html#ref-10" class="cite-ref">[10]</a></sup>, hence the culture around it being a key ingredient in preworkouts, energy drinks and synonymous with studying!).</li>
 <li><strong>Temperature.</strong> Keep the bedroom cool. Under a duvet, being too warm costs more deep and REM sleep than being cold does, and humid heat is worse again<sup><a href="https://www.kenreid.co.uk/blog/sleep-science.html#ref-6" class="cite-ref">[6]</a></sup>.</li>
 <li><strong>Exercise.</strong> Regular exercise has a moderate effect on how well people say they sleep, and small effects on how long they sleep and how fast they drop off<sup><a href="https://www.kenreid.co.uk/blog/sleep-science.html#ref-7" class="cite-ref">[7]</a></sup>. It's good for everything else too.</li>
 <li><strong>Hydration.</strong> Drink enough through the day, but not a pint right before bed unless you enjoy 3am trips to the toilet. People who slept six hours were more likely to be under-hydrated than people who slept eight<sup><a href="https://www.kenreid.co.uk/blog/sleep-science.html#ref-8" class="cite-ref">[8]</a></sup>.</li>
 </ol>

 <p>Everything past that (the scores, the supplements, the doom, the optimising) ranges from "might help" to overthinking and anxiety provoking. Unless your doctor says otherwise. </p>

 <p>One major tip that I think lies overarching most of these but isn't directly stated so far: stress will keep you up. There isn't a single pill to fixing stress, but these other things can help regardless, and whether you need to lower your ambition, worry less about things not in your control (therapy? meditation? prayer? journal keeping?) or change your personal circumstances to less stressful ones (new job? cut off that toxic person? move to somewhere warmer or more beautiful?), lowering your stress is also wonderful for your sleep habits, your mental health, and your physical health - even if it's easier said than done.</p>

 <figure class="kr-figure--solo">
 <img class="kr-figure-img" src="https://www.kenreid.co.uk/img/photography/thumb/474.webp" alt="A person standing by a railing at a frozen park pond on a snowy winter morning, with ducks on the ice and bare trees behind" loading="lazy" width="400" height="266" srcset="https://www.kenreid.co.uk/img/photography/thumb/474.webp 400w, https://www.kenreid.co.uk/img/photography/thumb/474@2x.webp 800w, https://www.kenreid.co.uk/img/photography/hero/474.webp 1892w" sizes="(min-width: 800px) 760px, 100vw">
 <figcaption class="figure-note"><span class="figure-copyright">&copy; Ken Reid. All rights reserved.</span></figcaption>
 </figure>

 <div class="faq-section">
 <h2>Common questions</h2>

 <details class="faq-item">
 <summary>Do I really need eight hours?</summary>
 <p>"Eight hours" is a population average, not a prescription for you. Adult sleep need varies, most people land somewhere in a 7-to-9-hour band, and a rare few are true short sleepers. Aim for routine over number of hours, and once you have that down, aim for the 7-9 band.</p>
 </details>

 <details class="faq-item">
 <summary>Should I throw out my sleep tracker, then?</summary>
 <p>Not necessarily, but use it as a rough trend line rather than a true signal of sleep quality or amount of sleep. </p>
 </details>

 <details class="faq-item">
 <summary>Is it true that lost sleep can never be recovered?</summary>
 <p>Somewhat. Chronic, long-term sleep deprivation does carry real cumulative risks, and you can't "bank" or "repay" sleep like money. But the harder claims (that one bad night causes permanent damage, or that catch-up sleep does nothing) go past the evidence. A rough night is survivable and normal, recovery sleep does help, and lying there convinced you've done irreversible harm is both wrong and terrible for tonight's sleep.</p>
 </details>
 </div>

 <h2 class="section-heading" id="references">References</h2>
 <ol class="references">
          <li id="ref-1">
            Windred, D. P., et al. (2024). Sleep regularity is a stronger predictor of mortality risk than sleep duration. <em>Sleep</em>, 47(1), zsad253. <a href="https://doi.org/10.1093/sleep/zsad253" target="_blank" rel="noopener">https://doi.org/10.1093/sleep/zsad253</a>
          </li>
          <li id="ref-2">
            Guzey, A. (2019/2022). Matthew Walker's "Why We Sleep" is riddled with scientific and factual errors. <a href="https://guzey.com/books/why-we-sleep/" target="_blank" rel="noopener">https://guzey.com/books/why-we-sleep/</a> (a critique; treat as a starting point, not a final word, and read the exchanges it prompted)
          </li>
          <li id="ref-3">
            Baron, K. G., Abbott, S., Jao, N., Manalo, N., &amp; Mullen, R. (2017). Orthosomnia: Are some patients taking the quantified self too far? <em>Journal of Clinical Sleep Medicine</em>, 13(2), 351&ndash;354. <a href="https://doi.org/10.5664/jcsm.6472" target="_blank" rel="noopener">https://doi.org/10.5664/jcsm.6472</a>
          </li>
          <li id="ref-4">
            Drake, C., Roehrs, T., Shambroom, J., &amp; Roth, T. (2013). Caffeine effects on sleep taken 0, 3, or 6 hours before going to bed. <em>Journal of Clinical Sleep Medicine</em>, 9(11), 1195&ndash;1200. <a href="https://doi.org/10.5664/jcsm.3170" target="_blank" rel="noopener">https://doi.org/10.5664/jcsm.3170</a>
          </li>
          <li id="ref-5">
            Caffeine. <em>Wikipedia</em> (accessed September 2026). <a href="https://en.wikipedia.org/wiki/Caffeine" target="_blank" rel="noopener">https://en.wikipedia.org/wiki/Caffeine</a>
          </li>
          <li id="ref-6">
            Okamoto-Mizuno, K., &amp; Mizuno, K. (2012). Effects of thermal environment on sleep and circadian rhythm. <em>Journal of Physiological Anthropology</em>, 31, 14. <a href="https://doi.org/10.1186/1880-6805-31-14" target="_blank" rel="noopener">https://doi.org/10.1186/1880-6805-31-14</a>
          </li>
          <li id="ref-7">
            Kredlow, M. A., Capozzoli, M. C., Hearon, B. A., Calkins, A. W., &amp; Otto, M. W. (2015). The effects of physical activity on sleep: a meta-analytic review. <em>Journal of Behavioral Medicine</em>, 38(3), 427&ndash;449. <a href="https://doi.org/10.1007/s10865-015-9617-6" target="_blank" rel="noopener">https://doi.org/10.1007/s10865-015-9617-6</a>
          </li>
          <li id="ref-8">
            Rosinger, A. Y., Chang, A.-M., Buxton, O. M., Li, J., Wu, S., &amp; Gao, X. (2019). Short sleep duration is associated with inadequate hydration: cross-cultural evidence from US and Chinese adults. <em>Sleep</em>, 42(2), zsy210. <a href="https://doi.org/10.1093/sleep/zsy210" target="_blank" rel="noopener">https://doi.org/10.1093/sleep/zsy210</a>
          </li>
          <li id="ref-9">
            McLellan, T. M., Caldwell, J. A., &amp; Lieberman, H. R. (2016). A review of caffeine's effects on cognitive, physical and occupational performance. <em>Neuroscience &amp; Biobehavioral Reviews</em>, 71, 294&ndash;312. <a href="https://doi.org/10.1016/j.neubiorev.2016.09.001" target="_blank" rel="noopener">https://doi.org/10.1016/j.neubiorev.2016.09.001</a>
          </li>
          <li id="ref-10">
            Guest, N. S., VanDusseldorp, T. A., Nelson, M. T., Grgic, J., Schoenfeld, B. J., Jenkins, N. D. M., Arent, S. M., Antonio, J., et al. (2021). International Society of Sports Nutrition position stand: caffeine and exercise performance. <em>Journal of the International Society of Sports Nutrition</em>, 18(1), 1. <a href="https://doi.org/10.1186/s12970-020-00383-4" target="_blank" rel="noopener">https://doi.org/10.1186/s12970-020-00383-4</a>
          </li>
        </ol>

        
 ]]></content:encoded>
    </item>
    <item>
      <title>What a Custom Domain Actually Involves</title>
      <link>https://www.kenreid.co.uk/blog/what-a-custom-domain-actually-involves.html</link>
      <guid>https://www.kenreid.co.uk/blog/what-a-custom-domain-actually-involves.html</guid>
      <pubDate>Sun, 27 Sep 2026 00:00:00 +0000</pubDate>
      <description>The custom domain takes about twenty minutes. Registrars, DNS records, HTTPS, and email at your own address, demystified.</description>
      <category>technology</category>
      <category>advice</category>
      <content:encoded><![CDATA[

 <p>I first made my website when I did my undergraduate degree, in 2012-2013. I hosted it via my university, as they had a means to drop an html file into your main directory and it would become your student homepage. It was a very simple affair, nothing like it is today. The thing I hated most about it was having to give my university's URL to let people see my page. I wanted it to represent me professionally, act as a CV / resume of sorts for future employers - and I knew upon leaving university I would lose the benefits of their hosting, so one of the things I had to look into was custom domains.</p>

 <p>It has its own vocabulary (registrars, DNS, A records, MX records), the settings pages kind of looks like a programmer's encyclopedia, and one wrong entry feels like it might break the internet for everyone else too. Nothing you do here is hard to undo, and you do most of it once then forget how it's done (until you write a blog post on it, I guess).</p>

 <div class="plain-english-box">
 <h2>Quick jargon guide</h2>
 <ul>
 <li><strong>Registrar:</strong> the company you rent your domain name from, annually. Renting is all anyone does. Domains are never owned outright.</li>
 <li><strong>DNS:</strong> the internet's phonebook. When someone types your domain, DNS answers "that name lives at this address". Your registrar gives you a settings page to write those entries.</li>
 <li><strong>A record:</strong> a DNS entry mapping your bare domain to a server's numeric IP address.</li>
 <li><strong>CNAME record:</strong> a DNS entry mapping a name to another name, like pointing <code>www.kenreid.co.uk</code> at GitHub's servers by name rather than number.</li>
 <li><strong>MX record:</strong> the DNS entry that routes your domain's <em>email</em>, entirely separately from the website.</li>
 <li><strong>Apex / bare domain:</strong> the domain with nothing in front: <code>kenreid.co.uk</code> as opposed to <code>www.kenreid.co.uk</code>.</li>
 <li><strong>HTTPS / TLS certificate:</strong> the padlock. Proof to browsers that the site at your name is really yours, issued free and automatically these days.</li>
 </ul>
 </div>

 <h2>Step one: rent the name</h2>

 <p>Pick a registrar (mine is Bluehost through historical inertia, and Cloudflare, Porkbun and Namecheap are the usual modern recommendations), search for the name, pay. A <code>.co.uk</code> or <code>.com</code> runs roughly &pound;10&ndash;15 a year. If you host via GitHub, this is your only expense - pretty cheap, right?</p>

 <p>Register it in your own account, not a friend's, not a freelancer's, and definitely not your employer's. Turn auto-renew on and keep the payment card current, because expired domains get bought by squatters within hours (if your website is big enough to warrant that). And treat the registrar account like a bank login (real password, two-factor), because whoever controls it controls everything downstream, including your email!</p>

 <p>On the name itself: your own name is a fine default for a personal site (this one is proof), shorter beats cleverer, and any mainstream ending is fine. The exotic endings (<code>.io</code>, <code>.dev</code>, <code>.pizza</code>) work identically but often cost more at renewal, and might make it difficult to convince people of your email address (I've had people ask me if "I'm sure" that my email address really does end with @kenreid.co.uk - yes, it does!). Introductory first-year discounts are the industry's favourite trick.</p>

 <h2>Step two: point it at your host</h2>

 <p>Your registrar's DNS page is where the twenty minutes happens, and you add a handful of records that say where web traffic should go. For this site, hosted on <a href="https://www.kenreid.co.uk/blog/hosting-photography-on-github-for-free.html">GitHub Pages</a>, that means four A records pointing the apex at GitHub's published IP addresses, and one CNAME record pointing <code>www</code> at my GitHub Pages hostname. Every host publishes the records it wants and the shape is always the same few lines. Mine, conceptually:</p>

 <div style="overflow-x:auto"><table class="kr-table">
 <thead><tr><th scope="col">Type</th><th scope="col">Name</th><th scope="col">Points to</th></tr></thead>
 <tbody>
 <tr><td>A</td><td><code>@</code> (the bare domain)</td><td><code>185.199.108.153</code></td></tr>
 <tr><td>A</td><td><code>@</code></td><td><code>185.199.109.153</code></td></tr>
 <tr><td>A</td><td><code>@</code></td><td><code>185.199.110.153</code></td></tr>
 <tr><td>A</td><td><code>@</code></td><td><code>185.199.111.153</code></td></tr>
 <tr><td>CNAME</td><td><code>www</code></td><td><code>drkenreid.github.io</code></td></tr>
 </tbody>
 </table></div>

 <p>Then you tell the host which domain to answer for. On GitHub Pages that's a one-line file named <code>CNAME</code> in the repository (mine says <code>www.kenreid.co.uk</code>), or the same setting in the repository's Pages settings, plus a checkbox to enforce HTTPS. The certificate is issued automatically within the hour, free, and renews itself forever. In 2012 the padlock cost real money and real ceremony, and now it is free generally.</p>

 <p>DNS changes take anywhere from minutes to a day to spread across the internet - be patient.</p>

 <figure>
 <img src="https://www.kenreid.co.uk/img/photography/thumb/42.webp" alt="Railway tracks running toward snow-covered Scottish hills under a bright sun and scattered clouds" loading="lazy" class="kr-figure-img" width="400" height="266" srcset="https://www.kenreid.co.uk/img/photography/thumb/42.webp 400w, https://www.kenreid.co.uk/img/photography/thumb/42@2x.webp 800w, https://www.kenreid.co.uk/img/photography/hero/42.webp 1200w" sizes="(min-width: 800px) 760px, 100vw">
 <figcaption class="figure-note"><span class="figure-copyright">&copy; Ken Reid. All rights reserved.</span> </figcaption>
 </figure>

 <h2>Step three (optional, recommended): email at your name</h2>

 <p>The domain routes email separately from the website, via MX records, so <code>ken@kenreid.co.uk</code> works without GitHub ever seeing an email. I pay Fastmail a few pounds a month to host the mailbox. Adding their MX records at the registrar took five minutes with their guide open in another tab.</p>

 <p>An email address you own outlives every provider, every job, and every free service's pivot to enshittification. Mine will follow me to any mail host forever, because the name it lives at is rented in my name.</p>

 <p>Whether your site's official address is the bare domain or the www one. Both work (one redirects to the other), but pick one and use it everywhere, because search engines treat them as different sites. This site standardised on <code>www.kenreid.co.uk</code> and every page declares it. <a href="https://www.kenreid.co.uk/blog/invisible-half-of-a-blog-post.html">The metadata post</a> covers the tags that keep that consistent.</p>

 <h2>What ongoing maintenance looks like</h2>

 <p>Approximately none. The domain renews itself annually (you get an email and a small charge), the certificate renews itself without telling you, and the DNS records sit untouched for years. The one day they change is the day you switch host or email provider. You edit a few lines, and everything else follows the name across.</p>

 <p>Register it in your own account, turn auto-renew on, and then ignore it for a year. Well worth the minor cost, in my opinion.</p>

 <h2>Steps, concisely</h2>

 <ol>
 <li><strong>Buy:</strong> register your domain and turn on auto-renew.</li>
 <li><strong>Point:</strong> add A and CNAME records at your registrar to send traffic to your host (<a href="https://docs.github.com/en/pages/configuring-a-custom-domain-for-your-github-pages-site/managing-a-custom-domain-for-your-github-pages-site" target="_blank" rel="noopener noreferrer">GitHub's DNS guide</a>).</li>
 <li><strong>Connect:</strong> tell your host your domain name and turn on HTTPS (<a href="https://www.kenreid.co.uk/blog/hosting-photography-on-github-for-free.html">hosting on GitHub Pages</a>).</li>
 <li><strong>Email (optional):</strong> add MX records to send mail to your provider (<a href="https://www.fastmail.help/hc/en-us/articles/360058753394-Custom-domains-with-Fastmail" target="_blank" rel="noopener noreferrer">Fastmail's custom domain guide</a>).</li>
 <li><strong>Forget:</strong> keep your payment details current, let auto-renew run, and use your chosen address everywhere (<a href="https://www.kenreid.co.uk/blog/invisible-half-of-a-blog-post.html">the metadata post</a>).</li>
 </ol>


 <div class="faq-section">
 <h2>Common questions</h2>

 <details class="faq-item">
 <summary>Can I break anything permanently in the DNS settings?</summary>
 <p>No. Wrong records make your site or email unreachable <em>while the records are wrong</em>. Fix the record and reality resumes, minutes to hours later. The two dangerous acts both live at the account level: letting the domain expire, and losing the registrar login. Guard those and everything else is reversible tinkering.</p>
 </details>

 <details class="faq-item">
 <summary>Should I buy the .com and .net and every other version too?</summary>
 <p>For a personal site, buy the one you'll use. Defensive registration of every ending is a business concern (and a registrar upsell that has been told to sound like security advice). Nobody is squatting the <code>.net</code> of a personal blog, and if they someday are, congratulations on the fame.</p>
 </details>

 <details class="faq-item">
 <summary>What about domain privacy / WHOIS protection?</summary>
 <p>Registration details go into a public directory unless the registrar masks them, and most now do it free by default. Check the box exists before paying. A registrar charging extra for privacy in 2026 is telling you something about their other prices.</p>
 </details>

 <details class="faq-item">
 <summary>My host offers a free subdomain. Why pay for a domain at all?</summary>
 <p><code>yourname.github.io</code> works and costs nothing, and for a project site it's fine. The problem is that the free name belongs to the host. Switch hosts and every link to your site breaks, forever. For &pound;12 a year, moving host becomes a settings change instead of a change of identity. I have never regretted the twelve quid.</p>
 </details>
 </div>

        
 ]]></content:encoded>
    </item>
    <item>
      <title>A Headline That Evolves Itself</title>
      <link>https://www.kenreid.co.uk/blog/a-headline-that-evolves-itself.html</link>
      <guid>https://www.kenreid.co.uk/blog/a-headline-that-evolves-itself.html</guid>
      <pubDate>Sat, 26 Sep 2026 00:00:00 +0000</pubDate>
      <description>The homepage headline is a hill climber painting 78 triangles until they read Hi, I&#x27;m Ken.</description>
      <category>technology</category>
      <content:encoded><![CDATA[<p><em>This post includes an interactive demo that runs live in the browser. <a href="https://www.kenreid.co.uk/blog/a-headline-that-evolves-itself.html">View it on the site</a> to play with it.</em></p>

 <p>My website has a bunch of different optimisers running in blog posts and a photograph of my face on the front. I think the optimisers are the best thing on this site, and I figured, why not show it right there on the homepage? So the second slide of the hero now runs one too, and what it's optimising itself into drawing the headline "Hi, I'm Ken". Here's how it works, and what it took to make it cheap enough to leave running on a homepage. Part of the series on <a href="https://www.kenreid.co.uk/series-how-this-site-is-built.html">how this site is built</a>.</p>

 <figure class="kr-figure--solo">
 <div class="kr-evolve-stage">
 
 </div>
 <figcaption class="figure-note">The homepage headline, running here in your browser. Scroll away and back to watch it build again.</figcaption>
 </figure>

 <div class="plain-english-box">
 <h2>Quick jargon guide</h2>
 <ul>
 <li><strong>Hill climbing:</strong> the simplest optimiser there is. Change your answer a bit, keep the change if it scored better, throw it away if it didn't, repeat forever.</li>
 <li><strong>(1+1) evolution strategy:</strong> hill climbing in evolution-strategy notation. One parent, one child per generation, and the better of the two survives.</li>
 <li><strong>Fitness function:</strong> the score. It decides what "better" means.</li>
 <li><strong>Genome:</strong> the numbers describing one candidate answer. Here, a list of triangles.</li>
 <li><strong>getImageData:</strong> the browser call that hands you the raw pixels of a canvas. It's slow, so how often you call it decides whether the page stays usable.</li>
 </ul>
 </div>

 <p>Roger Alsing's Mona Lisa came out in December 2008 and is one of the best demos of evolutionary computation. He took 50 semi-transparent polygons, changed one small thing at a time, kept the change only if the picture looked more like the painting than before, and after enough rounds of that he had something you'd recognise as da Vinci's.</p>

 <p>Text, therefore, should be an easier target than a face. Letters are high contrast and built from straight-ish strokes, and people will read a word from a rough smear of ink in roughly the right place well before they notice it's made of shapes. So the slide has a canvas, and on the canvas are 78 translucent triangles being nudged toward the pixels of "Hi, I'm Ken."</p>

 <h2>The whole algorithm</h2>

 <p>There's no population, no crossover and no selection pressure to tune. It's one candidate and one mutation at a time:</p>

 <pre><code class="language-javascript">for (var e = 0; e &lt; EVALS_PER_FRAME; e++) {
    var cand = genome.map(cloneShape);
    mutate(cand, scale);
    var s2 = score(cand);
    if (s2 &lt; best) {
        best = s2;
        genome = cand;
    }
}</code></pre>

 <p>The fitness function draws the candidate and compares it with the target, pixel by pixel:</p>

 <pre><code class="language-javascript">function score(g) {
    paint(g, fit, FIT_W, FIT_H);
    var d = fit.getImageData(0, 0, FIT_W, FIT_H).data;
    var err = 0;
    for (var i = 0, p = 0; i &lt; d.length; i += 4, p++) {
        var lum = (d[i] * 0.2126 + d[i + 1] * 0.7152 + d[i + 2] * 0.0722) / 255;
        var diff = lum * (d[i + 3] / 255) - target[p];
        err += diff * diff;
    }
    return err;
}</code></pre>

 <p>It's squared error against the brightness of the target, with the alpha folded in because the canvas sits transparent over a photograph, and lower is better. The target is the headline itself, drawn once into an offscreen canvas in the site's own font (Poppins).</p>

 <h2>Keeping it cheap</h2>

 <p>I score on a canvas of 176 by 48 pixels that is hidden from users. The canvas you see is up to 560 pixels wide on the page, which is 1,120 real pixels on a retina screen, and scoring at that size would mean reading back over 370,000 pixels per evaluation, 170 evaluations a frame. At 176 by 48 it's 8,448, and the letters are still readable at that size.</p>

 <p>I didn't let colour evolve either. Each triangle carries one number that puts it somewhere between the two ends of the site's photo gradient, which keeps the result on palette and takes three dimensions out of the search.</p>

 <p>Most mutations get rejected, and a rejected mutation changes nothing anyone can see, so the visible canvas is repainted at most once a frame and only if at least one of that frame's mutations was kept.</p>

 <p>The hero is a carousel of five slides, so four times out of five this one isn't on screen, and while it's off screen the loop checks whether its slide is the active one, sees it isn't, and does nothing. Once 1,800 evaluations in a row have failed to improve anything the loop idles, and when the slide comes back round it starts again from a fresh set of triangles, so whoever's looking gets to watch the headline being built instead of arriving at a finished one. The carousel can be paused, too, to give it more time.</p>

 <h2>What a screen reader gets</h2>

 <p>I marked the canvas <code>aria-hidden</code> so screen readers skip it, and put the real heading next to it as visually hidden text:</p>

 <pre><code class="language-html">&lt;h2 class="sr-only"&gt;Hi, I'm Ken.&lt;/h2&gt;
&lt;canvas class="kr-hero-evolve" aria-hidden="true"&gt;&lt;/canvas&gt;</code></pre>

 <p>The document outline doesn't change, a crawler sees a normal heading, and nobody has to sit through a description of an animation they can't see.</p>

 <h2>Was it worth it</h2>

 <p>It's one small file, and most of it is comments and setup. It swapped a static line of text for a working demo of evolutionary computation, the field my research is in, inside the first few seconds on the site. I'm normally against homepage animations that are only there to move, because they cost the reader something and give nothing back, but this one is a real optimiser running on your machine and you can read its fitness function further up this page.</p>

 <p>If you want the version with sliders and a population and all the knobs, that's <a href="https://www.kenreid.co.uk/blog/evolution-live.html">Evolution, Live</a>.</p>

        
 ]]></content:encoded>
    </item>
    <item>
      <title>The Inaccessibility of Photography</title>
      <link>https://www.kenreid.co.uk/blog/the-inaccessibility-of-photography.html</link>
      <guid>https://www.kenreid.co.uk/blog/the-inaccessibility-of-photography.html</guid>
      <pubDate>Sat, 26 Sep 2026 00:00:00 +0000</pubDate>
      <description>The gear treadmill makes photography look expensive, but my gallery was shot on a camera that now sells used from about a hundred dollars, and my phone.</description>
      <category>photography</category>
      <content:encoded><![CDATA[

 <p>In 1900 Eastman Kodak brought out a box camera called the Brownie and sold it for one dollar, which is about $40 in today's money (the name came partly from Frank Brownell of Rochester, New York, who made them for Eastman).<sup><a href="https://www.kenreid.co.uk/blog/the-inaccessibility-of-photography.html#ref-1" class="cite-ref">[1]</a></sup><sup><a href="https://www.kenreid.co.uk/blog/the-inaccessibility-of-photography.html#ref-2" class="cite-ref">[2]</a></sup> A hundred thousand of them sold in the first year, and the Franklin Institute, which has one in its collection, says it "helped to put photography into the hands of amateurs".</p>

 <p>A hundred and twenty-six years later, photography looks like a hobby for people with money. And, well, it largely is. If you go to any big sporting event and look at the journalist section you'll see a bunch of people with lenses about the size of my arm, and cost about 4x the price of my car. For one lens. Then if you look at discussion boards online, most of what I see is about gear: which body, which lens, which upgrade, what this year's model does that last year's didn't. It puts people off of a hobby that can actually be done with very little. You can take good photos on a camera that sells used for about a hundred dollars, or on the phone you already own, and edit them in software that costs nothing (except some time to learn).</p>

 <div class="plain-english-box">
 <h2>Quick jargon guide</h2>
 <ul>
 <li><strong>Body:</strong> the camera itself, without a lens. Lenses are sold separately and swap between bodies of the same mount.</li>
 <li><strong>DSLR:</strong> a digital single-lens reflex camera, the kind with a mirror that shows you the view through the lens. Generally refers to what people think of with regards to modern cameras.</li>
 <li><strong>APS-C:</strong> a sensor size smaller than a frame of 35mm film, common in cheaper interchangeable-lens cameras.</li>
 <li><strong>Raw file:</strong> the sensor's readings as recorded, before the camera turns them into a JPEG. More room to fix things when you edit.</li>
 <li><strong>DNG:</strong> Adobe's "Digital Negative" open raw format; many phones save raw files as DNG.</li>
 <li><strong>Open source:</strong> software whose code anyone can read, change and share, usually free to download.</li>
 <li><strong>Non-destructive editing:</strong> the editor stores your changes as instructions and never alters the original file.</li>
 </ul>
 </div>

 <h2>Musicians have a name for it</h2>

 <p>Gear Acquisition Syndrome, or GAS, is "the musicians' unrelenting urge to buy and own instruments and equipment as an anticipated catalyst of creative energy and bringer of happiness". That's from Jan-Peter Herbst and Jonas Menze, who wrote a whole book about it in 2021. They also write: "The urge is directed by the belief that acquiring another instrument will make one a better player."<sup><a href="https://www.kenreid.co.uk/blog/the-inaccessibility-of-photography.html#ref-3" class="cite-ref">[3]</a></sup> With "camera" in place of "instrument", that describes a good chunk of photography YouTube and TikTok.</p>

 <p>I've played guitar since I was about 11 and got into photography around 15. I get the urge to build up your gear library, who doesn't want new toys? It brings immediate satisfaction, fun, enjoyment, inspiration, while doing so with your existing gear is something of a chore before you actually enjoy it again. Plus - think of all the things new gear can do your current gear can't! But that is where creativity come into play, or sometimes, swallowing that urge and focusing on what your gear CAN do, putting new gear into the future. You'll enjoy your new gear even more with more skills and experience gained from your old gear.</p>

 <figure class="kr-figure--solo">
 <img class="kr-figure-img" src="https://www.kenreid.co.uk/img/photography/thumb/519.webp" alt="Blades of grass covered in raindrops, in dark blue-green light" loading="lazy" width="400" height="266" srcset="https://www.kenreid.co.uk/img/photography/thumb/519.webp 400w, https://www.kenreid.co.uk/img/photography/thumb/519@2x.webp 800w, https://www.kenreid.co.uk/img/photography/hero/519.webp 1920w" sizes="(min-width: 800px) 760px, 100vw">
 <figcaption class="figure-note"><span class="figure-copyright">&copy; Ken Reid. All rights reserved.</span> Canon EOS 60D, EF-S 55-250mm f/4-5.6 IS STM at 250mm, f/5.6, 1/400s, ISO 6400.</figcaption>
 </figure>

 <h2>My camera came out in 2010</h2>

 <p>Almost every photo in my gallery was shot on a Canon EOS 60D (though I think a couple linger from my old 350D, and a couple of phone shots are there, too). Canon announced it on 26 August 2010, an 18 megapixel APS-C DSLR with a screen that flips out, and it went on sale in the US at $1,099 for the body, or $1,399 with an 18-135mm kit lens.<sup><a href="https://www.kenreid.co.uk/blog/the-inaccessibility-of-photography.html#ref-4" class="cite-ref">[4]</a></sup><sup><a href="https://www.kenreid.co.uk/blog/the-inaccessibility-of-photography.html#ref-5" class="cite-ref">[5]</a></sup> That was a lot of money in 2010. This week the used camera shop MPB lists 60D bodies from $97 to $214.<sup><a href="https://www.kenreid.co.uk/blog/the-inaccessibility-of-photography.html#ref-6" class="cite-ref">[6]</a></sup></p>

 <p>Canon has replaced it three times since, with the 70D, the 80D and then the 90D in August 2019, and Wikipedia now lists a mirrorless camera, the EOS R10, as the 90D's successor, and that's excluding the myriad of better series out there.<sup><a href="https://www.kenreid.co.uk/blog/the-inaccessibility-of-photography.html#ref-7" class="cite-ref">[7]</a></sup><sup><a href="https://www.kenreid.co.uk/blog/the-inaccessibility-of-photography.html#ref-8" class="cite-ref">[8]</a></sup> At my old workplace, I had the opportunity to use a Canon EOS 5D Mark III. Here's a brief comparison:<sup><a href="https://www.kenreid.co.uk/blog/the-inaccessibility-of-photography.html#ref-7" class="cite-ref">[7]</a></sup><sup><a href="https://www.kenreid.co.uk/blog/the-inaccessibility-of-photography.html#ref-17" class="cite-ref">[17]</a></sup></p>

 <figure>
 <img class="kr-figure-img" src="https://www.kenreid.co.uk/blog/img/inaccessibility/canon-60d.webp" alt="A black Canon EOS 60D with a 15-85mm zoom lens, seen from the front at an angle" loading="lazy" width="600" height="538">
 <figcaption class="figure-note">EOS 60D. <a href="https://commons.wikimedia.org/wiki/File:Canon_EOS_60D_01.jpg" target="_blank" rel="noopener noreferrer">Maksim Sidorov</a>, CC BY 3.0.</figcaption>
 </figure>
 <figure>
 <img class="kr-figure-img" src="https://www.kenreid.co.uk/blog/img/inaccessibility/canon-5d-mark-iii.webp" alt="A black Canon EOS 5D Mark III with a large L-series lens, seen from the front at an angle" loading="lazy" width="600" height="538">
 <figcaption class="figure-note">EOS 5D Mark III. <a href="https://commons.wikimedia.org/wiki/File:Canon_EOS_5D_Mark_III.jpg" target="_blank" rel="noopener noreferrer">decltype</a>, CC BY-SA 3.0.</figcaption>
 </figure>

 <div style="overflow-x:auto"><table class="kr-table">
 <thead><tr><th scope="col"><span class="sr-only">Spec</span></th><th scope="col">EOS 60D</th><th scope="col">EOS 5D Mark III</th></tr></thead>
 <tbody>
 <tr><th scope="row">Announced</th><td>August 2010</td><td>March 2012</td></tr>
 <tr><th scope="row">Launch price</th><td>$1,099</td><td>$3,499</td></tr>
 <tr><th scope="row">Current price (used)</th><td>$97 to $214<sup><a href="https://www.kenreid.co.uk/blog/the-inaccessibility-of-photography.html#ref-6" class="cite-ref">[6]</a></sup></td><td>$154 to $579<sup><a href="https://www.kenreid.co.uk/blog/the-inaccessibility-of-photography.html#ref-18" class="cite-ref">[18]</a></sup></td></tr>
 <tr><th scope="row">Sensor</th><td>18 MP, APS-C</td><td>22.3 MP, full frame</td></tr>
 <tr><th scope="row">Autofocus points</th><td>9</td><td>61</td></tr>
 <tr><th scope="row">Burst</th><td>5.3 frames/s</td><td>6 frames/s</td></tr>
 <tr><th scope="row">ISO (expanded)</th><td>100 to 6400 (12800)</td><td>100 to 25600 (102400)</td></tr>
 <tr><th scope="row">Screen</th><td>3in, flips out</td><td>3.2in, fixed</td></tr>
 <tr><th scope="row">Cards</th><td>SD</td><td>CF + SD</td></tr>
 <tr><th scope="row">Weight</th><td>675 g</td><td>860 g</td></tr>
 </tbody>
 </table></div>
 <p class="kr-muted-note">Prices and weights are for the body alone, without a lens.</p>

 <p>The gallery has ~500 photos in it, about a decade of self-taught landscapes, urban decay and pets, and I'm happy with them. I'm sure that a fancier camera and lens would have improved the shots, sure, but the grass is always greener. The 60D doesn't even have GPS, so I've been putting the <a href="https://www.kenreid.co.uk/blog/putting-my-photos-on-a-map.html">photo map</a> together by hand. It shoots 5.3 frames a second and tops out at ISO 6400 (12800 if you push it), which is plenty for a hill at sunset or a patient dog, and not what I'd take to a football match under floodlights (not that I'd go to a football match, anyhow).<sup><a href="https://www.kenreid.co.uk/blog/the-inaccessibility-of-photography.html#ref-7" class="cite-ref">[7]</a></sup> Most of a landscape photo is being in the right place when the light is good and pointing the thing in a sensible direction. The lenses I have are nothing fancy. I make use of a kit lens and a cheap telephoto lens I got for around $100. The only thing I need is more time, not gear! </p>

 <figure class="kr-figure--solo">
 <img class="kr-figure-img" src="https://www.kenreid.co.uk/img/photography/thumb/514.webp" alt="A red car parked behind a screen of dark green leaves" loading="lazy" width="400" height="266" srcset="https://www.kenreid.co.uk/img/photography/thumb/514.webp 400w, https://www.kenreid.co.uk/img/photography/thumb/514@2x.webp 800w, https://www.kenreid.co.uk/img/photography/hero/514.webp 1920w" sizes="(min-width: 800px) 760px, 100vw">
 <figcaption class="figure-note"><span class="figure-copyright">&copy; Ken Reid. All rights reserved.</span> Canon EOS 60D, EF-S 55-250mm f/4-5.6 IS STM at 55mm, f/4, 1/80s, ISO 800.</figcaption>
 </figure>

 <h2>The camera you already have</h2>

 <p>Rise Above Research, who somehow came up with a number, estimate that around 1.95 trillion photos will be taken worldwide in 2026, with smartphones accounting for the vast majority.<sup><a href="https://www.kenreid.co.uk/blog/the-inaccessibility-of-photography.html#ref-9" class="cite-ref">[9]</a></sup></p>

 <p>In 2009 the photographer Chase Jarvis published a book of pictures taken on his iPhone, a visual notebook of a year of his life, and called it <em>The Best Camera Is The One That's With You</em>.<sup><a href="https://www.kenreid.co.uk/blog/the-inaccessibility-of-photography.html#ref-10" class="cite-ref">[10]</a></sup></p>

 <h2>Editing for nothing</h2>

 <p>Snapseed is Google's photo editor for phones. The App Store listing promises "30+ pro tools and filters" with "no subscriptions, purchases, ads, or watermarks".<sup><a href="https://www.kenreid.co.uk/blog/the-inaccessibility-of-photography.html#ref-11" class="cite-ref">[11]</a></sup> It edits raw files too: DNG files on Android, and on an iPhone or iPad raw files from a list of cameras that includes the 60D (and my old 350D).<sup><a href="https://www.kenreid.co.uk/blog/the-inaccessibility-of-photography.html#ref-12" class="cite-ref">[12]</a></sup> Opening a raw file goes straight to the Develop tool, with exposure, shadows, highlights, contrast, structure, temperature and tint.</p>

 <p>On a computer there's darktable, which calls itself "an open source photography workflow application and raw developer", free under the GPL, with builds for Windows, macOS and Linux.<sup><a href="https://www.kenreid.co.uk/blog/the-inaccessibility-of-photography.html#ref-13" class="cite-ref">[13]</a></sup><sup><a href="https://www.kenreid.co.uk/blog/the-inaccessibility-of-photography.html#ref-14" class="cite-ref">[14]</a></sup> Its editing is "fully non-destructive", so the original raw file is never changed and the full image is only worked out on export. Its camera support page lists the 60D with white balance presets and a noise profile.<sup><a href="https://www.kenreid.co.uk/blog/the-inaccessibility-of-photography.html#ref-15" class="cite-ref">[15]</a></sup> RawTherapee is the other big free one, also GPL, also on all three systems.<sup><a href="https://www.kenreid.co.uk/blog/the-inaccessibility-of-photography.html#ref-16" class="cite-ref">[16]</a></sup></p>

 <p>Sharing photos doesn't have to cost anything either. The full-size files for my gallery are <a href="https://www.kenreid.co.uk/blog/hosting-photography-on-github-for-free.html">hosted for free</a>.</p>

 <h2>If you're starting out</h2>

 <p>This is what I'd tell a friend who wanted to get into photography and thought they couldn't afford it:</p>

 <ul>
 <li>Use your phone for a month first. Take far too many photos, delete most of them, as is tradition.</li>
 <li>Put Snapseed on it and edit the ones you keep.</li>
 <li>If you still want a camera with a viewfinder and lenses that come off, look at used bodies a few generations old, or borrow a friends. Check darktable's camera support page before you buy, so you know the raw files will open.</li>

 </ul>

 <p>If this gets you out with an old camera or your phone, show me what you took in the comments, and tell me what it was shot on.</p>

 <div class="faq-section">
 <h2>Common questions</h2>

 <details class="faq-item">
 <summary>Should I buy a 60D because you use one?</summary>
 <p>No. Buy whatever older body you can find cheaply in good condition with a lens you like, and check that your editor opens its raw files first. The 60D is only in this post because it's the one I have.</p>
 </details>
 </div>

 <h2 class="section-heading" id="references">References</h2>
 <ol class="references">
  <li id="ref-1">The Franklin Institute. Kodak Brownie camera. <em>Collections</em> (accessed September 2026). <a href="https://fi.edu/en/science-and-education/collection/kodak-brownie-camera" target="_blank" rel="noopener noreferrer">https://fi.edu/en/science-and-education/collection/kodak-brownie-camera</a></li>
  <li id="ref-2">$1 in 1900 is worth $39.88 today. <em>CPI Inflation Calculator</em>, Official Data Foundation, based on US Bureau of Labor Statistics data (accessed September 2026). <a href="https://www.officialdata.org/us/inflation/1900?amount=1" target="_blank" rel="noopener noreferrer">https://www.officialdata.org/us/inflation/1900?amount=1</a></li>
  <li id="ref-3">Herbst, J.-P., &amp; Menze, J. (2021). <em>Gear Acquisition Syndrome: Consumption of instruments and technology in popular music</em>. University of Huddersfield Press. <a href="https://doi.org/10.5920/GearAcquisition.fulltext" target="_blank" rel="noopener noreferrer">https://doi.org/10.5920/GearAcquisition.fulltext</a></li>
  <li id="ref-4">Canon EOS 60D DSLR announced and previewed. (2010, 26 August). <em>DPReview</em>. <a href="https://www.dpreview.com/articles/1692435631/canoneos60d/" target="_blank" rel="noopener noreferrer">https://www.dpreview.com/articles/1692435631/canoneos60d/</a></li>
  <li id="ref-5">Miller, R. (2010, 26 August). Canon EOS 60D: 18 megapixels and 1080p video flexes its articulating screen this September. <em>Engadget</em>. <a href="https://www.engadget.com/2010-08-26-canon-eos-60d-18-megapixels-and-1080p-video-flexes-its-articula.html" target="_blank" rel="noopener noreferrer">https://www.engadget.com/2010-08-26-canon-eos-60d-18-megapixels-and-1080p-video-flexes-its-articula.html</a></li>
  <li id="ref-6">MPB. Used Canon EOS 60D (accessed 25 September 2026). <a href="https://www.mpb.com/en-us/product/canon-eos-60d" target="_blank" rel="noopener noreferrer">https://www.mpb.com/en-us/product/canon-eos-60d</a></li>
  <li id="ref-7">Canon EOS 60D. <em>Wikipedia</em> (accessed September 2026). <a href="https://en.wikipedia.org/wiki/Canon_EOS_60D" target="_blank" rel="noopener noreferrer">https://en.wikipedia.org/wiki/Canon_EOS_60D</a></li>
  <li id="ref-8">Canon EOS 90D. <em>Wikipedia</em> (accessed September 2026). <a href="https://en.wikipedia.org/wiki/Canon_EOS_90D" target="_blank" rel="noopener noreferrer">https://en.wikipedia.org/wiki/Canon_EOS_90D</a></li>
  <li id="ref-9">Rise Above Research. (2026, February). 2026 worldwide image capture forecast: 2025&ndash;2030. <a href="https://riseaboveresearch.com/rar-reports/2026-worldwide-image-capture-forecast-2025-2030/" target="_blank" rel="noopener noreferrer">https://riseaboveresearch.com/rar-reports/2026-worldwide-image-capture-forecast-2025-2030/</a></li>
  <li id="ref-10">Jarvis, C. (2009). <em>The Best Camera Is The One That's With You: iPhone photography</em>. New Riders. <a href="https://www.peachpit.com/store/best-camera-is-the-one-thats-with-you-iphone-photography-9780321684783" target="_blank" rel="noopener noreferrer">https://www.peachpit.com/store/best-camera-is-the-one-thats-with-you-iphone-photography-9780321684783</a></li>
  <li id="ref-11">Google LLC. Snapseed. <em>App Store</em> (accessed September 2026). <a href="https://apps.apple.com/us/app/snapseed/id439438619" target="_blank" rel="noopener noreferrer">https://apps.apple.com/us/app/snapseed/id439438619</a></li>
  <li id="ref-12">Google. Develop: RAW editing. <em>Snapseed Help</em> (accessed September 2026). <a href="https://support.google.com/snapseed/answer/6312515?hl=en" target="_blank" rel="noopener noreferrer">https://support.google.com/snapseed/answer/6312515?hl=en</a></li>
  <li id="ref-13">darktable. About (accessed September 2026). <a href="https://www.darktable.org/about/" target="_blank" rel="noopener noreferrer">https://www.darktable.org/about/</a></li>
  <li id="ref-14">darktable. Install (version 5.6.1, accessed September 2026). <a href="https://www.darktable.org/install/" target="_blank" rel="noopener noreferrer">https://www.darktable.org/install/</a></li>
  <li id="ref-15">darktable. Camera support (accessed September 2026). <a href="https://www.darktable.org/resources/camera-support/" target="_blank" rel="noopener noreferrer">https://www.darktable.org/resources/camera-support/</a></li>
  <li id="ref-16">RawTherapee (accessed September 2026). <a href="https://www.rawtherapee.com/" target="_blank" rel="noopener noreferrer">https://www.rawtherapee.com/</a></li>
  <li id="ref-17">Canon EOS 5D Mark III. <em>Wikipedia</em> (accessed September 2026). <a href="https://en.wikipedia.org/wiki/Canon_EOS_5D_Mark_III" target="_blank" rel="noopener noreferrer">https://en.wikipedia.org/wiki/Canon_EOS_5D_Mark_III</a></li>
  <li id="ref-18">MPB. Used Canon EOS 5D Mark III (accessed 26 September 2026). <a href="https://www.mpb.com/en-us/product/canon-eos-5d-mark-iii" target="_blank" rel="noopener noreferrer">https://www.mpb.com/en-us/product/canon-eos-5d-mark-iii</a></li>
 </ol>

        

 ]]></content:encoded>
    </item>
    <item>
      <title>Every Card Has a Sketch</title>
      <link>https://www.kenreid.co.uk/blog/every-card-has-a-sketch.html</link>
      <guid>https://www.kenreid.co.uk/blog/every-card-has-a-sketch.html</guid>
      <pubDate>Sun, 20 Sep 2026 00:00:00 +0000</pubDate>
      <description>Move your mouse over a card on the blog and the cover starts doing something. How the sketches work.</description>
      <category>technology</category>
      <content:encoded><![CDATA[

 <p>Move your mouse over any card on the <a href="https://www.kenreid.co.uk/blog.html">blog listing</a> and the cover starts doing something. The <a href="https://www.kenreid.co.uk/blog/tabu-search-live.html">tabu search</a> one has queens on a chessboard getting banned from squares they just left. The <a href="https://www.kenreid.co.uk/blog/why-i-love-latex.html">LaTeX</a> one types out <code>\section{Why}</code> on the left while a little page sets itself on the right. I made one for every post (67 at the time of writing), then got a bit carried away and did the homepage stats, the publications and the book wall as well. This is how they work. Part of the series on <a href="https://www.kenreid.co.uk/series-how-this-site-is-built.html">how this site is built</a>.</p>

 <div class="kr-sketch-demo" data-live="every-card-has-a-sketch" data-live-under tabindex="0">
 <p class="kr-sketch-demo__hint">This post has one too, of course. Hover here (or tab to it) to watch it.</p>
 </div>

 <div class="plain-english-box">
 <h2>Quick jargon guide</h2>
 <ul>
 <li><strong>Canvas:</strong> the browser element you draw on with code instead of markup. Every sketch here is a canvas laid over a card's cover photo.</li>
 <li><strong>requestAnimationFrame:</strong> the browser's "call me back before the next repaint". Sixty times a second on most screens, a hundred and twenty on some laptops. This bit me, see below.</li>
 <li><strong>MutationObserver:</strong> a way to get told when something new lands on the page, so code can deal with cards that show up after it loaded.</li>
 </ul>
 </div>

 <h2>What a sketch is</h2>

 <p>Each one is a function, in essence. It gets a width, a height and a random number generator, and hands back two more functions: <code>step</code>, which moves things along, and <code>draw</code>, which paints. Here's the offline-reading one with some fat trimmed:</p>

 <pre><code class="language-javascript">def('reading-this-site-offline', function (w, h, rnd) {
    var t = 0;   // frame counter; the engine advances this, not the browser's clock
    return {
        // one cycle is 300 frames: 90 online, then 210 offline, then round again
        step: function () { t = (t + 1) % 300; },
        draw: function (ctx) {
            var off = t &gt; 90;
            // the wifi symbol: three arcs, faded out once "off"
            for (var i = 0; i &lt; 3; i++) {
                ctx.strokeStyle = off ? alpha('#ffffff', 0.15) : C.ink;
                ctx.beginPath(); ctx.arc(w * 0.2, h * 0.5, 10 + i * 10, 3.6, 5.8); ctx.stroke();
            }
            // a little page, sliding left to right, looping every 100 frames
            var k = phase(t * 1.5, 100);
            rect(ctx, w * 0.62 + w * 0.22 * k, h * 0.42, 14, 18, C.b);
            // the caption underneath, swapping with the wifi state
            text(ctx, off ? 'offline, still reading' : 'online', w * 0.5, h * 0.9, 9, off ? C.b : C.dim, 700, 'center');
        }
    };
});</code></pre>

 <h2>The engine</h2>

 <p>First, the engine finds the cards. Cards on this site get rendered by five or six different scripts at different moments (the blog listing, the series pages, the related posts under each article, the homepage), and my first version made each of those scripts call the engine once it had finished rendering. Now the engine watches the page for anything being added and hooks up any card it has a sketch for.</p>

 <p>Second, it steps sixty times a second whatever the screen is doing. The sketches count frames, and my first version stepped once per <code>requestAnimationFrame</code>, which is fine on a 60 Hz monitor but silly on a 120 Hz one: every ant ran at double speed, every tide went out in half the time. In a throttled background tab they crawled instead. So the engine keeps its own clock now and steps once per sixtieth of a second of real time, with a cap so a tab that's been hidden for a minute doesn't try to catch up all in one go.</p>

 <p>Third, it doesn't load until it's needed. The three files come to 41 KB compressed, which is a lot to send a phone for a hover effect it can't trigger (no mouse, no hover, nothing to do). So no page includes them any more. The first time a mouse moves, and only if the browser says hovering is a thing it can do and you haven't asked for reduced motion, the shared script fetches all three. On a desktop they turn up about a second before you could want them, which is close enough for me, and phones don't store them at all.</p>

 <h2>Keeping them from rotting</h2>

 <p>There are two checks. A commit gets refused if any post in the listing has no sketch, so I can't publish without drawing one, to make sure I don't forget (something I do frequently without lots of checkpoints for myself!). And the <a href="https://www.kenreid.co.uk/blog/my-website-has-a-test-suite.html">test suite</a> builds every sketch, runs it for 120 frames, and fails if it throws or the canvas is still blank at the end. </p>

 <p>If you're reading this on a phone you've seen none of it, and that's fine. Nothing to hover with, nothing to download, and the cards are just cards.</p>

        
 ]]></content:encoded>
    </item>
    <item>
      <title>Hyper-Heuristics, Live</title>
      <link>https://www.kenreid.co.uk/blog/hyper-heuristics-live.html</link>
      <guid>https://www.kenreid.co.uk/blog/hyper-heuristics-live.html</guid>
      <pubDate>Sat, 19 Sep 2026 00:00:00 +0000</pubDate>
      <description>A hyper-heuristic timetabling exams live in your browser: four move heuristics compete for a learning manager&#x27;s attention while a Great Deluge water level drains.</description>
      <category>ai</category>
      <content:encoded><![CDATA[<p><em>This post includes an interactive demo that runs live in the browser. <a href="https://www.kenreid.co.uk/blog/hyper-heuristics-live.html">View it on the site</a> to play with it.</em></p>

 <p>Nine posts in, every search strategy in this series has handed you its dials and waited:</p>
 <ul>
 <li><a href="https://www.kenreid.co.uk/blog/ant-colony-live.html">the ant colony</a> wanted an evaporation rate</li>
 <li><a href="https://www.kenreid.co.uk/blog/evolution-live.html">the genetic algorithm</a> and its <a href="https://www.kenreid.co.uk/blog/genetic-programming-live.html">program-breeding cousin</a> wanted mutation rates and population sizes</li>
 <li><a href="https://www.kenreid.co.uk/blog/simulated-annealing-live.html">simulated annealing</a> put you in charge of the thermostat</li>
 <li><a href="https://www.kenreid.co.uk/blog/particle-swarm-live.html">the swarm</a> asked for inertia, own memory and peer pressure</li>
 <li><a href="https://www.kenreid.co.uk/blog/variable-neighbourhood-search-live.html">variable neighbourhood search</a> needed to know how many neighbourhoods to shake through</li>
 <li><a href="https://www.kenreid.co.uk/blog/cma-es-live.html">CMA-ES</a> adapted its own step size, but still took a population and a starting sigma from you</li>
 <li><a href="https://www.kenreid.co.uk/blog/differential-evolution-live.html">differential evolution</a> asked for F and CR</li>
 <li><a href="https://www.kenreid.co.uk/blog/tabu-search-live.html">tabu search</a>, one post ago, wanted its tenure set</li>
 </ul>

 <p>Hyper heuristics are inherently <i>meta</i>. It searches for the right way to search, optimizing by choosing the best optimizer.</p>

 <p>It's a heuristic for choosing heuristics, and the field calls it a hyper-heuristic.</p>

 

 <p>The problem this demo is chewing on is exam timetabling, which is very close to what I solved during my studies. My PhD went on rostering BT's engineers, and exam timetabling is the same family of problem with students in place of shifts: easy to state, brutal to solve, and impossible to explain to friends and family what makes it so difficult. </p>

 <div class="plain-english-box">
 <h2>Quick jargon guide</h2>
 <ul>
 <li><strong>Hyper-heuristic:</strong> a search strategy that operates on other search strategies. It decides which move-making heuristic to apply next, rather than editing the solution itself.</li>
 <li><strong>Low-level heuristic:</strong> one of the workers: a simple move rule, like "swap two exams", that actually edits the timetable.</li>
 <li><strong>Domain barrier:</strong> the wall between manager and workers. The hyper-heuristic never sees exams, students, or rooms; only whether the cost went up or down.</li>
 <li><strong>Selection vs generation hyper-heuristics:</strong> selection hyper-heuristics choose among existing heuristics (this post); generation hyper-heuristics assemble brand-new heuristics from components, often with genetic programming.</li>
 <li><strong>Great Deluge:</strong> an acceptance rule with a single moving part: a water level that starts generous and drains steadily. Any move that keeps the cost at or below the level is accepted, uphill or not.</li>
 <li><strong>Acceptance criterion:</strong> the rule that decides whether a proposed move is kept or thrown away. Annealing uses temperature and probability; Great Deluge uses a water level.</li>
 <li><strong>Hard constraint:</strong> a rule the timetable must satisfy (no student sitting two exams at once). Violations are possible but only if really struggling to find solutions.</li>
 <li><strong>Soft constraint:</strong> a preference (no back-to-back exams for the same student). </li>
 </ul>
 </div>

 <h2>Twenty exams, seven slots, three rooms</h2>

 <p>Any of us who have been to college / university have likely experienced the hellish end of term exam period. Most students stick to specific degree programs, so if you're a 'generic' student, your exams will likely be spaced fairly evenly. If you, however, are doing a triple major of sports, theoretical physics and nursing, you are the most likely to be screwed over, as screwing up people with that specific triple-threat (just you) is less of a failure than messing up everyone who just took generic psychology classes exams.</p>

 <p>The instance below is twenty exams, labelled A to T, to be placed into seven timeslots with three rooms available per slot. A seeded random conflict graph decides which pairs of exams share students. From that, one number to shrink. Two conflicting exams in the same slot is a hard constraint violation and costs '100', because somewhere a student is expected to be in two rooms at once.</p>

 <p>Conflicting exams in adjacent slots cost 1 to 3 (scaled by how many students they share), because back-to-back exams are legal, but far from ideal. And a fourth exam squeezed into a three-room slot costs 10 per extra exam. That weighting is the standard trick for mixing hard and soft constraints into one objective.</p>

 <p>A timetable is not usable until the hard count reaches zero. Everything after that is how an "acceptable" timetables progresses towards "ok", and hopefully, "good", or even better, "optimal".</p>

 <h2>The manager and the workers</h2>

 <p>Four low-level heuristics do the standard heuristic solving business. <strong>H1 nudge</strong> moves one random exam to a random slot. <strong>H2 swap</strong> picks two exams and trades their slots. <strong>H3 worst-to-best</strong> finds the exam contributing the most penalty and moves it to the slot where it does the least damage. <strong>H4 ruin</strong> kicks three random exams to random slots, a small controlled demolition.</p>

 <p>Above them sits the manager - it keeps a score for each worker, rewards a worker whenever its move is accepted and improves the cost, and lets every score decay over time. Even the manager's least favourite heuristic keeps getting the odd chance, because a heuristic useless so far might be useful later down the line when the circumstances change.</p>

 <p>Cowling, Kendall and Soubeiga used the same pattern to schedule a sales summit<sup><a href="https://www.kenreid.co.uk/blog/hyper-heuristics-live.html#ref-2" class="cite-ref">[2]</a></sup>, in the paper that named the modern field.</p>

 <h2>Intentionally Blind</h2>

 <p>The manager can't see the timetable. It doesn't know what an exam is. It has no idea students exist. It just has four levers to pull and keeps records of what is working well just now, what isn't, and keeps chugging.</p>

 <p>All it ever observes is "I pulled lever three and the cost went down by 40" or "I pulled lever one and the move was rejected". That blindness is called the domain barrier, and it sounds like a handicap but it's necessary.</p>

 <p>Because the manager only ever hears cost deltas, you can unplug the four timetabling workers, plug in "move a nurse to another shift" and "swap two delivery routes", and the same manager, unchanged, starts learning which of <em>those</em> levers earns its keep. The domain knowledge lives entirely in the cheap, disposable workers. Burke (who was briefly my supervisor at Stirling) and colleagues' survey<sup><a href="https://www.kenreid.co.uk/blog/hyper-heuristics-live.html#ref-3" class="cite-ref">[3]</a></sup> maps how far that one trick works.</p>

 <h2>Great Deluge: the tide going out</h2>

 <p>The manager decides which move to try. Something still has to decide which results to keep: Gunter Dueck's Great Deluge, published in 1993<sup><a href="https://www.kenreid.co.uk/blog/hyper-heuristics-live.html#ref-1" class="cite-ref">[1]</a></sup> does the trick.</p>

 <p>Dueck told it as a story about rain - it rains without end, the water rises, and a hiker who wants to end up on a high peak just has one goal: keep their feet dry!</p>

 <p>For minimisation, just turn it upside down: cost is depth, and the search is a fish that must stay submerged. The tide goes out, the pool drains, and the fish is herded into the deepest hollow of the reef it can still reach, or mermaids, or whatever, it's a metaphor you get the idea.</p>

 <p>Compare the two acceptance rules. Simulated annealing accepts a worsening move with probability <code>exp(&minus;&Delta;/T)</code>, which means choosing a starting temperature, a cooling schedule, and possibly reheats: a small family of dials, as the <a href="https://www.kenreid.co.uk/blog/simulated-annealing-live.html">demo</a> showed. Great Deluge accepts any move that lands at or below the water level. Uphill, downhill, sideways: if you are under the surface, woohoo.</p>

 <h2>Watch the manager learn</h2>

 <p>Watch the probability bars first. That row is the manager's opinion of its workers, updating live. Early on, H3 hoovers up credit, because on a messy random timetable the biggest offender is obvious. Then the easy repairs run out, H3 stalls, decay eats its lead, and you can watch H1 and H4 claw back probability as disruption starts earning acceptances that greedy repair cannot find.</p>

 <p>Then watch the flood chart. The shaded water is the accepted region, the line is the cost swimming inside it, and whenever the tide presses down on the line, the level is doing the forcing.</p>

 

 <h2>The tide, too fast and too slow</h2>

 <p>Drain the water too fast, by pushing the drop rate to maximum, and the level crashes below any cost the search can actually reach. From then on nothing passes the test at all: the accepted-moves stat falls to zero while the hard-conflict count sits stranded above it, and after 600 rejections in a row the demo gives up and says so. The search hasn't converged. The tide stranded it partway down the reef, which is the deluge's version of quenching hot metal too quickly.</p>

 <p>Drain it too slowly and the level stays miles above the cost, the rule accepts almost everything, and the search wanders: plenty of motion, nothing pushing the cost down. The chart shows a cost line bouncing around far below a distant water surface but going nowhere in particular. All the forcing comes from the tide.</p>

 <p>Then turn learning to zero. The manager stops keeping score and picks workers uniformly at random: a committee with no memory. Restart a few runs at learning zero, then a few at full learning, and compare how long the hard-conflict counter takes to hit zero. </p>

 <h2>The ladder up</h2>

 <p>There is a tradition in this series of me telling you that some dial matters enormously and then making you tune it by hand: mutation rates, evaporation rates, cooling schedules, F and CR, tabu tenures. <a href="https://www.kenreid.co.uk/blog/cma-es-live.html">CMA-ES</a> staged the first escape from that tradition, adapting its own search distribution as it ran; a hyper-heuristic takes the idea to its limit and moves the whole choice of <em>which heuristic to use</em> inside the run.</p>

 <p>It doesn't remove the design decisions: I still chose the workers, the reward, the decay and the floor. Turtles, all the way up.</p>

 <div class="faq-section">
 <h2>Common questions</h2>

 <details class="faq-item">
 <summary>Why not just use the best heuristic all the time?</summary>
 <p>The great thing about hyper-heuristics is that they can work on varied problems. If you have an especially large number of exams vs. more or less constraints vs. lots of "ok" solutions and it being hard to traverse them etc.</p>
 </details>

 <details class="faq-item">
 <summary>How is Great Deluge different from simulated annealing?</summary>
 <p>Both accept worsening moves early and become strict late. Annealing is probabilistic: a worse move survives a coin flip whose odds shrink with the damage done and with the falling temperature. Great Deluge is a deterministic threshold: any move landing at or below the water level is accepted, however bad, and any move above it is rejected, however slight. </p>
 </details>

 <details class="faq-item">
 <summary>Is the manager just a multi-armed bandit?</summary>
 <p>It is a close cousin. Choosing which lever to pull based on observed payoffs is the bandit problem, though here the payoffs are nonstationary, so the scores decay rather than accumulate forever. The demo above is a selection hyper-heuristic, choosing among fixed workers. Generation hyper-heuristics go further and build new heuristics from raw ingredients, often with genetic programming - the Burke survey in the references maps the family tree.</p>
 </details>
 </div>

 <h2 class="section-heading" id="references">References</h2>

 <ol class="references">
          <li id="ref-1">
            Dueck, G. (1993). New Optimization Heuristics: The Great Deluge Algorithm and the Record-to-Record Travel. <em>Journal of Computational Physics</em>, 104(1), 86&ndash;92. <a href="https://doi.org/10.1006/jcph.1993.1010" target="_blank" rel="noopener">https://doi.org/10.1006/jcph.1993.1010</a>
          </li>
          <li id="ref-2">
            Cowling, P., Kendall, G., &amp; Soubeiga, E. (2001). A Hyperheuristic Approach to Scheduling a Sales Summit. In <em>Practice and Theory of Automated Timetabling III (PATAT 2000)</em>, LNCS 2079, 176&ndash;190. <a href="https://doi.org/10.1007/3-540-44629-X_11" target="_blank" rel="noopener">https://doi.org/10.1007/3-540-44629-X_11</a>
          </li>
          <li id="ref-3">
            Burke, E. K., Gendreau, M., Hyde, M., Kendall, G., Ochoa, G., &Ouml;zcan, E., &amp; Qu, R. (2013). Hyper-heuristics: a survey of the state of the art. <em>Journal of the Operational Research Society</em>, 64(12), 1695&ndash;1724. <a href="https://doi.org/10.1057/jors.2013.71" target="_blank" rel="noopener">https://doi.org/10.1057/jors.2013.71</a>
          </li>
        </ol>

        

 ]]></content:encoded>
    </item>
    <item>
      <title>Photographing Strangers</title>
      <link>https://www.kenreid.co.uk/blog/photographing-strangers.html</link>
      <guid>https://www.kenreid.co.uk/blog/photographing-strangers.html</guid>
      <pubDate>Wed, 16 Sep 2026 00:00:00 +0000</pubDate>
      <description>Street photography is legal in most places and comfortable in almost none of them. Where the law actually lands across 52 jurisdictions, and the four tests I use before I raise a camera at someone.</description>
      <category>photography</category>
      <category>philosophy</category>
      <content:encoded><![CDATA[

 <p>I've taken photos of many people without them knowing or consenting. Sometimes, they do know, but don't consent - I'll take a photo and they turn around and see my lens pointing at them. So far no one has asked me to delete any photos, but I've definitely had stares and glares.</p>

 <p>Street photography stirs some interesting questions. It's legal in most public places (though not in the UAE or South Korea, and France is far stricter than visitors expect), but there are lots of legal ways you can be an asshole.</p>

 <figure class="kr-figure--solo">
   <img src="https://www.kenreid.co.uk/img/photography/thumb/213.webp" alt="A young man in a grey hooded top crossing an empty paved square, half turned towards the camera" loading="lazy" class="kr-figure-img" width="400" height="266" srcset="https://www.kenreid.co.uk/img/photography/thumb/213.webp 400w, https://www.kenreid.co.uk/img/photography/thumb/213@2x.webp 800w, https://www.kenreid.co.uk/img/photography/hero/213.webp 1200w" sizes="(min-width: 800px) 760px, 100vw">
   <figcaption class="figure-note"><span class="figure-copyright">&copy; Ken Reid. All rights reserved.</span></figcaption>
 </figure>

 <h2>Legality</h2>

 <p>In most jurisdictions, including the UK and US, photographing people in public spaces is broadly legal. There's a reduced expectation of privacy in public, and the whole documentary tradition depends on that freedom.</p>

 <p>"Broadly legal" covers three quite different rules, though, and which one you are under depends on where your feet are. Wikimedia Commons maintains a country-by-country list of consent requirements for pictures of identifiable people, because it has to: it hosts the photographs. Of the 52 jurisdictions it documents, the split runs like this.</p>

 <table class="values-grid">
 <thead>
 <tr><th>What the law asks of you</th><th class="score">Jurisdictions</th><th>Notes</th></tr>
 </thead>
 <tbody>
 <tr><td>Nothing: shoot and publish without asking</td><td class="score">18</td><td>Including India, the US, Iran<sup>*</sup>, the UK and South Africa</td></tr>
 <tr><td>Consent to publish, but not to shoot</td><td class="score">18</td><td>Including Russia, Ethiopia, Mexico, the Philippines and Germany</td></tr>
 <tr><td>Consent before you press the shutter</td><td class="score">14</td><td>Including China, Indonesia, Brazil, Japan and Turkey</td></tr>
 <tr><td>Unsettled, or different inside one country</td><td class="score">2</td><td>Canada (province by province) and Hong Kong</td></tr>
 </tbody>
 </table>

 <p class="figure-note" style="text-align: left;">Counts from the <a href="https://commons.wikimedia.org/wiki/Commons:Country_specific_consent_requirements" target="_blank" rel="noopener noreferrer">Wikimedia Commons country-specific consent requirements</a> guideline. It documents 52 jurisdictions out of roughly 195, and says plainly that a country's absence from the list proves nothing: the UAE is not on it, and criminalises photographing a person without consent under Article 44 of Federal Decree-Law 34 of 2021. <sup>*</sup>Iran sits in the top row on one narrow 2007 statute about women-only spaces and private gatherings, which shows how much work "with exceptions" is doing in every row above. None of this is legal advice.</p>

 <p>That says nothing about the ethics or social implications, of course. Cultures vary from town to town, never mind across country borders. But legal is the floor, and plenty of legal things are unkind, so treating the law as the whole of the ethics is unwise. The question I care about is "should I, and how", and the law can perhaps give insight into some broad strokes of social acceptance, but not ethics.</p>

 <p>The context has changed since the old masters. When Cartier-Bresson or Vivian Maier shot a stranger, that photograph lived in a print, a gallery, a book, or perhaps in a dusty old film cartridge in a chest somewhere. A bounded, slow world.</p>

 <p>A photograph now can be uploaded, geolocated, reverse-image-searched and attached to a name in minutes. The same click that once made a fleeting image now makes a searchable, permanent, findable record of a person who never agreed to be in it. So the ethics can't be inherited wholesale from a pre-internet age. And that cuts both ways: we live in the era of Flock cameras, smartphones, and TikTok, Twitch, Facebook and YouTube livestreamers.</p>

 <figure class="kr-figure--solo">
   <img src="https://www.kenreid.co.uk/img/photography/thumb/163.webp" alt="Pedestrians walking away from the camera along a busy city pavement past cafe tables and a parked bicycle" loading="lazy" class="kr-figure-img" width="400" height="257" srcset="https://www.kenreid.co.uk/img/photography/thumb/163.webp 400w, https://www.kenreid.co.uk/img/photography/thumb/163@2x.webp 800w, https://www.kenreid.co.uk/img/photography/hero/163.webp 1200w" sizes="(min-width: 800px) 760px, 100vw">
   <figcaption class="figure-note"><span class="figure-copyright">&copy; Ken Reid. All rights reserved.</span></figcaption>
 </figure>

 <h2>A working framework</h2>

 <p>We can also consider paparazzi, as the worst kind of "well, it's legal" defence. Sure, it's legal, but camping outside someone's home, place of work or school to take non-consensual photos, hoping to make a dollar selling them to some awful supermarket rag, is still a choice you made (who needs AI slop when you can peruse human created slop?).</p>

 <p>I think there's something on the other side of this that I haven't heard discussed: photography paralysis. Being so afraid of the first that you never take a plain photograph of human life at all. That's a real loss, because people are some of the most important things we can photograph. Someone just living their life, representing a common interest, feeling and life can echo something shared, capture beautiful moments and collective sadness. There is art in people being people, and capturing a fleeting moment forever. Not capturing that is almost like destroying a beautiful painting or poem.</p>

 <figure class="kr-figure--solo">
   <img src="https://www.kenreid.co.uk/img/photography/thumb/212.webp" alt="A busker playing guitar against a shopfront on a cobbled street at dusk, the lit lane running away behind them" loading="lazy" class="kr-figure-img" width="400" height="266" srcset="https://www.kenreid.co.uk/img/photography/thumb/212.webp 400w, https://www.kenreid.co.uk/img/photography/thumb/212@2x.webp 800w, https://www.kenreid.co.uk/img/photography/hero/212.webp 1200w" sizes="(min-width: 800px) 760px, 100vw">
   <figcaption class="figure-note"><span class="figure-copyright">&copy; Ken Reid. All rights reserved.</span></figcaption>
 </figure>

 <h2>How I draw my bounds</h2>

 <p><strong>The dignity test.</strong> Before I publish (and often before I shoot), I ask whether the image honours the person or exploits them. A photograph that shows a stranger as interesting, dignified, fully human is a different object from one that invites the viewer to laugh at them, or gawk at them, or one that is really just virtue signalling from me.</p>

 <p>If the picture's whole appeal is that the subject is a spectacle, I'll have basically made a zoo and considered that person an animal.</p>

 <figure class="kr-figure--solo">
   <img src="https://www.kenreid.co.uk/img/photography/thumb/275.webp" alt="An older man in a patterned shirt and cap sitting on a stool in the sun outside a dark shopfront, black and white" loading="lazy" class="kr-figure-img" width="400" height="266" srcset="https://www.kenreid.co.uk/img/photography/thumb/275.webp 400w, https://www.kenreid.co.uk/img/photography/thumb/275@2x.webp 800w, https://www.kenreid.co.uk/img/photography/hero/275.webp 1200w" sizes="(min-width: 800px) 760px, 100vw">
   <figcaption class="figure-note"><span class="figure-copyright">&copy; Ken Reid. All rights reserved.</span></figcaption>
 </figure>

 <p><strong>The power gradient.</strong> This is the one that does the most work for me. Photograph across or up the power gradient, rarely down it.</p>

 <p>A candid of a confident person striding through their city is wonderful. A candid of someone at their most vulnerable (homeless, grieving, drunk, struggling) turns their worst moment into my content. It's the same worry I had about <a href="https://www.kenreid.co.uk/blog/abandoned-places-i-photograph.html">ruin porn</a>: a beautiful photograph of someone's suffering is still a photograph of suffering, and making it beautiful doesn't mean you're not personally benefitting from another's suffering. That doesn't mean you can't take photos of these things, but you need to be more creative in your photos. Avoiding faces or other identifying marks (tattoos, nametags) means you can show, for example, homelessness in Las Vegas, without it being exploitation. It can even be powerful, eye-opening, educational and a rallying cry.</p>

 <figure class="kr-figure--solo">
   <img src="https://www.kenreid.co.uk/img/photography/thumb/231.webp" alt="A man stretched out along a park bench in the afternoon, arms spread across the backrest, black and white" loading="lazy" class="kr-figure-img" width="400" height="249" srcset="https://www.kenreid.co.uk/img/photography/thumb/231.webp 400w, https://www.kenreid.co.uk/img/photography/thumb/231@2x.webp 800w, https://www.kenreid.co.uk/img/photography/hero/231.webp 1200w" sizes="(min-width: 800px) 760px, 100vw">
   <figcaption class="figure-note"><span class="figure-copyright">&copy; Ken Reid. All rights reserved.</span></figcaption>
 </figure>

 <p><strong>The ask-after option.</strong> You don't always have to choose between a stolen candid and a stiff posed shot. Shoot first, then approach, show them the frame, offer to delete it or to send it. It turns a taking into something closer to a collaboration, and people are delighted more often than not in my experience. It doesn't cost the "purity" of the unaware candid but it does buy a clear conscience and, often, can make someone's day (and maybe get you an extra Instagram follower if that's something you care about).</p>

 <figure class="kr-figure--solo">
   <img src="https://www.kenreid.co.uk/img/photography/thumb/252.webp" alt="A couple walking away through a stone archway, one arm draped over the other's shoulder, black and white" loading="lazy" class="kr-figure-img" width="400" height="266" srcset="https://www.kenreid.co.uk/img/photography/thumb/252.webp 400w, https://www.kenreid.co.uk/img/photography/thumb/252@2x.webp 800w, https://www.kenreid.co.uk/img/photography/hero/252.webp 1200w" sizes="(min-width: 800px) 760px, 100vw">
   <figcaption class="figure-note"><span class="figure-copyright">&copy; Ken Reid. All rights reserved.</span></figcaption>
 </figure>

 <p><strong>Consider obvious boundaries.</strong> Is the person you want to photograph on their own property, e.g. through a window or somewhere else they would normally expect privacy? Or are you in their home with them - if so, consider asking - taking photos inside someone's home can feel like an invasion of privacy to them.</p>

 <h2>Use the discomfort</h2>

 <p>Documentary photography of any kind requires you to have an <em>opinion about your subject</em>, not merely a technically good composition. The discomfort I feel raising a camera at a stranger keeps me asking the dignity question and checking the power gradient, so don't squash it down entirely, it can be informative, or at least help you check yourself.</p>

 <figure class="kr-figure--solo">
   <img src="https://www.kenreid.co.uk/img/photography/thumb/198.webp" alt="A woman in a sun hat crossing a bright square, a coat over one arm and a wheeled shopping bag in the other hand" loading="lazy" class="kr-figure-img" width="400" height="233" srcset="https://www.kenreid.co.uk/img/photography/thumb/198.webp 400w, https://www.kenreid.co.uk/img/photography/thumb/198@2x.webp 800w, https://www.kenreid.co.uk/img/photography/hero/198.webp 1200w" sizes="(min-width: 800px) 760px, 100vw">
   <figcaption class="figure-note"><span class="figure-copyright">&copy; Ken Reid. All rights reserved.</span></figcaption>
 </figure>

 <p>So I don't want to talk myself out of the hesitation, and I don't want you to either. The best street photography was made by people who clearly loved their subjects, and you can see it in the frames: the humans in them are seen, not caught.</p>

 <p>Keep the hesitation. Ask the dignity question, check the gradient, leave other people's kids out of it, and take the photograph when the answers come out right. So, Wheaton's Law.</p>

 <h2>Some of the frames</h2>

 <figure class="kr-figure--solo">
   <div class="photo-grid">
     <img src="https://www.kenreid.co.uk/img/photography/thumb/35.webp" alt="A lone figure standing at the end of a floating jetty on dark water, wooded hills behind, black and white" loading="lazy" width="400" height="196" srcset="https://www.kenreid.co.uk/img/photography/thumb/35.webp 400w, https://www.kenreid.co.uk/img/photography/thumb/35@2x.webp 800w, https://www.kenreid.co.uk/img/photography/hero/35.webp 1200w" sizes="(min-width: 800px) 373px, (min-width: 576px) 50vw, 100vw">
     <img src="https://www.kenreid.co.uk/img/photography/thumb/46.webp" alt="A woman with a large shoulder bag walking across a sunlit square in front of an arcaded stone building" loading="lazy" width="400" height="266" srcset="https://www.kenreid.co.uk/img/photography/thumb/46.webp 400w, https://www.kenreid.co.uk/img/photography/thumb/46@2x.webp 800w, https://www.kenreid.co.uk/img/photography/hero/46.webp 1200w" sizes="(min-width: 800px) 373px, (min-width: 576px) 50vw, 100vw">
     <img src="https://www.kenreid.co.uk/img/photography/thumb/64.webp" alt="Two men crossing a wide pedestrian street, one striding away and one in a hooded top with a backpack" loading="lazy" width="400" height="266" srcset="https://www.kenreid.co.uk/img/photography/thumb/64.webp 400w, https://www.kenreid.co.uk/img/photography/thumb/64@2x.webp 800w, https://www.kenreid.co.uk/img/photography/hero/64.webp 1200w" sizes="(min-width: 800px) 373px, (min-width: 576px) 50vw, 100vw">
     <img src="https://www.kenreid.co.uk/img/photography/thumb/81.webp" alt="Two wrestlers grappling in a sand pit at an outdoor event, spectators lining the edge" loading="lazy" width="400" height="266" srcset="https://www.kenreid.co.uk/img/photography/thumb/81.webp 400w, https://www.kenreid.co.uk/img/photography/thumb/81@2x.webp 800w, https://www.kenreid.co.uk/img/photography/hero/81.webp 1200w" sizes="(min-width: 800px) 373px, (min-width: 576px) 50vw, 100vw">
     <img src="https://www.kenreid.co.uk/img/photography/thumb/169.webp" alt="A man walking away down a wide boardwalk past shuttered units and a liquor store sign" loading="lazy" width="400" height="266" srcset="https://www.kenreid.co.uk/img/photography/thumb/169.webp 400w, https://www.kenreid.co.uk/img/photography/thumb/169@2x.webp 800w, https://www.kenreid.co.uk/img/photography/hero/169.webp 1200w" sizes="(min-width: 800px) 373px, (min-width: 576px) 50vw, 100vw">
     <img src="https://www.kenreid.co.uk/img/photography/thumb/202.webp" alt="Two men in silhouette crossing a sunlit square, backlit against pale paving" loading="lazy" width="400" height="400" srcset="https://www.kenreid.co.uk/img/photography/thumb/202.webp 400w, https://www.kenreid.co.uk/img/photography/thumb/202@2x.webp 800w, https://www.kenreid.co.uk/img/photography/hero/202.webp 1200w" sizes="(min-width: 800px) 373px, (min-width: 576px) 50vw, 100vw">
     <img src="https://www.kenreid.co.uk/img/photography/thumb/221.webp" alt="A man in a beanie standing outside a shopfront holding a hand-lettered sign reading 'fuck you', black and white" loading="lazy" width="400" height="266" srcset="https://www.kenreid.co.uk/img/photography/thumb/221.webp 400w, https://www.kenreid.co.uk/img/photography/thumb/221@2x.webp 800w, https://www.kenreid.co.uk/img/photography/hero/221.webp 1200w" sizes="(min-width: 800px) 373px, (min-width: 576px) 50vw, 100vw">
     <img src="https://www.kenreid.co.uk/img/photography/thumb/277.webp" alt="Two people sitting in silhouette on a riverbank under a stone bridge, kayaks on the water beyond" loading="lazy" width="400" height="263" srcset="https://www.kenreid.co.uk/img/photography/thumb/277.webp 400w, https://www.kenreid.co.uk/img/photography/thumb/277@2x.webp 800w, https://www.kenreid.co.uk/img/photography/hero/277.webp 1200w" sizes="(min-width: 800px) 373px, (min-width: 576px) 50vw, 100vw">
     <img src="https://www.kenreid.co.uk/img/photography/thumb/278.webp" alt="A person in a hooded top sitting alone on a riverbank looking out over the water, a slogan printed across the back of the hood" loading="lazy" width="400" height="266" srcset="https://www.kenreid.co.uk/img/photography/thumb/278.webp 400w, https://www.kenreid.co.uk/img/photography/thumb/278@2x.webp 800w, https://www.kenreid.co.uk/img/photography/hero/278.webp 1200w" sizes="(min-width: 800px) 373px, (min-width: 576px) 50vw, 100vw">
   </div>
   <figcaption class="figure-note"><span class="figure-copyright">&copy; Ken Reid. All rights reserved.</span></figcaption>
 </figure>

 <div class="faq-section">
 <h2>Common questions</h2>

 <details class="faq-item">
 <summary>Isn't asking permission going to kill the candid, spontaneous quality?</summary>
 <p>For the unaware candid, yes. That's what the ask-after option is for: shoot the spontaneous frame first, then approach. You keep the candid moment and add consent. That being said, of course be safe. Don't approach (or even photograph, ideally) people who you suspect might react poorly.</p>
 </details>

 <details class="faq-item">
 <summary>Doesn't all this hand-wringing just mean you should never do street photography?</summary>
 <p>It takes some social confidence, and there's always an element of being unsafe when dealing with strangers. I think the "hand-wringing" is just about making sure we maintain our ethics while conducting our artform.</p>
 </details>

 <details class="faq-item">
 <summary>What about candids where the person is just part of a wider scene?</summary>
 <p>Eh, it varies. Some laws don't care, some do, and ethically, I think there's definitely a level of "well, you are in public, people will perceive you - if you don't want to be perceived, don't be in public" that can be applied to group photos much more so than individual shots.</p>
 </details>
 </div>

        
 ]]></content:encoded>
    </item>
    <item>
      <title>Ruin and Recreate, Live</title>
      <link>https://www.kenreid.co.uk/blog/ruin-and-recreate-live.html</link>
      <guid>https://www.kenreid.co.uk/blog/ruin-and-recreate-live.html</guid>
      <pubDate>Sun, 13 Sep 2026 00:00:00 +0000</pubDate>
      <description>Most search algorithms improve a solution by nudging it. Ruin and recreate demolishes part of it and rebuilds, which works better on rosters. The method behind my 2018 rostering paper, running in your browser.</description>
      <category>data science</category>
      <content:encoded><![CDATA[<p><em>This post includes an interactive demo that runs live in the browser. <a href="https://www.kenreid.co.uk/blog/ruin-and-recreate-live.html">View it on the site</a> to play with it.</em></p>

 <p>I started my Ph.D. in 2015 under Dr. Jingpeng Li, a kindly and intelligent man who was very patient with me going down the wrong roads and floundering over fairly fundamental and basic math. He had some interesting ideas on how to modify existing algorithms to improve their capabilities, and let me toy with them to begin to understand some of that fundamental math, as well as get a feel for how search algorithms, at their core, actually function.</p>

<p>Most search algorithms take a working solution, change one thing, and keep the change if it helped. That works until the solution is tangled, at which point every single change makes things worse and the search sits there, surrounded by improvements it cannot reach because each one requires three simultaneous moves.</p>

 <p>Ruin and recreate knocks a hole in the solution and rebuilds the solution. This is the method behind <a href="https://scholar.google.com/citations?view_op=view_citation&amp;hl=en&amp;user=bwl2u00AAAAJ&amp;citation_for_view=bwl2u00AAAAJ:Se3iqnhoufwC" target="_blank" rel="noopener">a rostering paper I wrote in 2018</a><sup><a href="https://www.kenreid.co.uk/blog/ruin-and-recreate-live.html#ref-1" class="cite-ref">[1]</a></sup> with colleagues at Stirling, York and BT, using a variant of Jingpeng's own algorithm: Evolutionary Ruin and Stochastic Recreate<sup><a href="https://www.kenreid.co.uk/blog/ruin-and-recreate-live.html#ref-2" class="cite-ref">[2]</a></sup>, where the wrecking ball is aimed by fitness rather than swung blindly. The demo below is that method on a simple roster. Part of the series on <a href="https://www.kenreid.co.uk/series-research-live.html">research, live</a>.</p>

 <div class="plain-english-box">
 <h2>Quick jargon guide</h2>
 <ul>
 <li><strong>Roster:</strong> who works which shift on which day. Here, twelve staff over fourteen days, each on an early, late, or night shift, or off.</li>
 <li><strong>Fitness:</strong> one number saying how good a roster is, out of 100. Following the paper, hard constraints (covering demand, legal rest) are 95% of the score and soft ones (fair shares, no long runs) are the last 5%.</li>
 <li><strong>Local search:</strong> repeatedly make a small change and keep it if the fitness rises.</li>
 <li><strong>Local optimum:</strong> a solution where every small change makes things worse, which does not mean it is any good.</li>
 <li><strong>Large neighbourhood search:</strong> the family this belongs to<sup><a href="https://www.kenreid.co.uk/blog/ruin-and-recreate-live.html#ref-3" class="cite-ref">[3]</a></sup>. Instead of small changes, make one enormous change and repair the damage intelligently.</li>
 <li><strong>Evolutionary ruin:</strong> the paper's twist. Every allocation gets its own fitness score, and the weaker an allocation, the more likely the ruin phase is to demolish it.</li>
 </ul>
 </div>

 <h2>The demo</h2>

 <p>Each row is a person, each column a day. Cells are the shift they are working. The rows under the grid are the demand check: for each day and shift, how many staff over or under the requirement, so -2 on the night row means two short and +1 means one spare. A working cell carries the same figure when its shift is out of balance, which marks the allocations that are surplus and the ones holding up a short day. The panel under the stats scores each constraint separately: the two hard ones, which have to hold, and the two soft ones the last five per cent of the fitness is spent on.</p>

 <p>One step is one full ruin and recreate, under the same simulated annealing control the paper uses. The ruined cells flash before they are refilled, so you can see the size and shape of the hole. The default strategy is the paper's: score every allocation, then demolish the probabilistically weakest. Switch to the blind strategies to see what the aiming is worth: scattered cells, a person's whole fortnight, or everybody's Tuesday.</p>

 

 <h2>Why breaking it on purpose works</h2>

 <p>A roster is full of constraints that interlock. Moving one person off Tuesday night leaves Tuesday night short, so the move is rejected, so the person stays. Every individual change is bad, and yet the roster as a whole is nowhere near as good as it could be. That is a local optimum with high walls (you try to climb them and only fall right back down, you need to jump out of there entirely).</p>

 <p>Removing thirty assignments at once does not have to be an improvement, because the recreate step is not obliged to put them back where they were. It rebuilds greedily against the fitness function, which means it can discover an arrangement that no sequence of single moves would have reached, since each intermediate step in that sequence would have been rejected.</p>

 <p>You can see this in action in the demo: small ruins converge smoothly and stall early. Large ruins jump around and take longer per step, since there is more to rebuild. The paper's answer is this: if you know which allocations are weak, a small, well-aimed ruin can do the work of a large blind one.</p>

 <h2>Aim the wrecking ball, or swing it harder</h2>

 <p>I measured all four strategies on this instance. The score is the median number of steps to first reach a fitness of 99.5, over five seeds, with a 200-step cap; "never" means the median run did not get there at all. Lower is faster:</p>

 <table class="values-grid">
 <thead><tr><th>Ruin size</th><th class="score">Scattered</th><th class="score">Whole people</th><th class="score">Whole days</th><th class="score">Weakest (the paper)</th></tr></thead>
 <tbody>
 <tr><td>8 cells</td><td class="score">22</td><td class="score">never</td><td class="score">31</td><td class="score">10</td></tr>
 <tr><td>16 cells</td><td class="score">9</td><td class="score">never</td><td class="score">31</td><td class="score">8</td></tr>
 <tr><td>32 cells</td><td class="score">10</td><td class="score">23</td><td class="score">17</td><td class="score">16</td></tr>
 <tr><td>56 cells</td><td class="score">6</td><td class="score">20</td><td class="score">12</td><td class="score">16</td></tr>
 <tr><td>80 cells</td><td class="score">7</td><td class="score">10</td><td class="score">5</td><td class="score">17</td></tr>
 </tbody>
 </table>

 <p>The fitness-guided ruin is fastest when the holes are small, eight or ten steps with a demolition of only eight cells, and gets no benefit from swinging harder, because it was never relying on volume in the first place. The blind strategies are the mirror image: they need size. Scattered cells come good from sixteen cells up; ruining everybody's Tuesday is the single fastest setting in the table at eighty cells, seven whole columns of the roster at a time.</p>

 <p>Whole-people ruin at small sizes is the table's disaster zone, and the reason generalises. Eight or sixteen cells is one person's fortnight: freeing a single row while the other eleven stay fixed leaves the recreate step boxed in by everything it did not touch. Structured damage only pays when the structure being suspended is big enough to rearrange.</p>

 <p>So the destroy operator and its size are not two parameters, they are one decision: aim, or swing big. The paper's selection operator decides, and its reward is reaching the same quality with a fraction of the demolition, which matters when every ruined cell costs rebuild time. Blind volume gets there too, eventually, by suspending so many constraints at once that the rebuild has room to rearrange everything.</p>

 <h2>What the demo is not</h2>

 <p>The real problem had a great deal more to it: engineers with different skills and a first-skill preference, shift rules dictating lengths, start-time windows and shift counts, alternating week patterns, UK working-time law, and two consecutive rest days per week as a soft target. The instances ran to around 200 employees, drawn from a domain of roughly 25,000 field engineers. The fitness function works with eight hard constraints and four soft ones, weighted 95 to 5.</p>

 <p>What survives compression is the shape of the method, which is the part that transfers. Score it, break the weak parts, rebuild, and let the temperature decide what to keep.</p>

 <div class="faq-section">
 <h2>Common questions</h2>

 <details class="faq-item">
 <summary>What makes it "evolutionary"?</summary>
 <p>The ruin phase runs on component fitness, in two mini-phases the paper calls selection and mutation. Selection scores every allocation and demolishes the probabilistically weakest, so bad genes die faster than good ones. Mutation then removes a few allocations at random, regardless of fitness, which keeps the search exploring even when every component looks healthy. Survival of the fittest, applied to the pieces of one schedule rather than to a population of schedules.</p>
 </details>

 <details class="faq-item">
 <summary>Is this just simulated annealing with extra steps?</summary>
 <p>SA is in there, but as the referee rather than the boxer. The engine is the ruin and rebuild; annealing referees, deciding whether the rebuilt roster replaces the current one, with energy modelled as inverse fitness, and its falling temperature is what stops the destruction of a nearly finished schedule late in the run. </p>
 </details>

 <details class="faq-item">
 <summary>Why greedy repair rather than an optimal one?</summary>
 <p>Because optimal repair of a thirty-cell hole is itself a hard problem, and you need to do it thousands of times. Greedy is fast, which buys iterations, and iterations buy quality. Where the repair can be made optimal cheaply, doing so is usually worth it: that variant is called large neighbourhood search with an optimal repair.</p>
 </details>

 <details class="faq-item">
 <summary>How big should the ruin be?</summary>
 <p>The amount of demolition emerges from the selection and mutation probabilities, which were tuned with a Taguchi design of experiments, and it falls naturally over the run: as the schedule improves, component fitness rises and protects good allocations from selection, while the falling temperature damps disruption late on. The table above shows why that self-regulation is the right instinct.</p>
 </details>

 <details class="faq-item">
 <summary>Does this beat a solver?</summary>
 <p>Not on a small clean instance. A solver hands you an answer with a proof attached, and this hands you an answer. The demo can announce a perfect roster only because the score in this toy stops at 100, so hitting the ceiling is the proof. Give it a real objective with no ceiling and the search has no way of knowing whether it is done. It wins when the instance is large, the constraints are awkward to express in a solver's language, and you need a good answer in seconds rather than a perfect one overnight. Most operational researchers care more about a good enough solution in time over a perfect solution a week late.</p>
 </details>
 </div>

 <h2 class="section-heading" id="references">References</h2>

 <ol class="references">
          <li id="ref-1">
            Reid, K. N., Li, J., Veerapen, N., Swan, J., McCormick, A., Kern, M., &amp; Owusu, G. (2018). Shift Scheduling and Employee Rostering: An Evolutionary Ruin &amp; Stochastic Recreate Solution. In <em>2018 10th Computer Science and Electronic Engineering (CEEC)</em>, 19&ndash;23. IEEE. <a href="https://scholar.google.com/citations?view_op=view_citation&amp;hl=en&amp;user=bwl2u00AAAAJ&amp;citation_for_view=bwl2u00AAAAJ:Se3iqnhoufwC" target="_blank" rel="noopener">Google Scholar</a>
          </li>
          <li id="ref-2">
            Li, J., Bai, R., Shen, Y., &amp; Qu, R. (2015). Search with evolutionary ruin and stochastic rebuild: A theoretic framework and a case study on exam timetabling. <em>European Journal of Operational Research</em>, 242(3), 798&ndash;806.
          </li>
          <li id="ref-3">
            Schrimpf, G., Schneider, J., Stamm-Wilbrandt, H., &amp; Dueck, G. (2000). Record Breaking Optimization Results Using the Ruin and Recreate Principle. <em>Journal of Computational Physics</em>, 159(2), 139&ndash;171. <a href="https://doi.org/10.1006/jcph.1999.6413" target="_blank" rel="noopener">https://doi.org/10.1006/jcph.1999.6413</a>
          </li>
        </ol>

        
 ]]></content:encoded>
    </item>
    <item>
      <title>Leading a Horse to Water</title>
      <link>https://www.kenreid.co.uk/blog/leading-a-horse-to-water.html</link>
      <guid>https://www.kenreid.co.uk/blog/leading-a-horse-to-water.html</guid>
      <pubDate>Sun, 06 Sep 2026 00:00:00 +0000</pubDate>
      <description>You give feedback to a colleague. They thank you for it, and a week later nothing has changed. Three checks before you push harder, one change of approach, a budget, and a date.</description>
      <category>advice</category>
      <content:encoded><![CDATA[<p><em>This post includes an interactive demo that runs live in the browser. <a href="https://www.kenreid.co.uk/blog/leading-a-horse-to-water.html">View it on the site</a> to play with it.</em></p>

        <p>I'm doing a master's in organizational leadership, and one of the first things I've learned is that leadership is not a status only found in specific roles, it's something we all do every day. Leading projects, leading people, making decisions on next steps, risk assessments, and practicing your social skills. One such example I have been thinking much about recently is providing feedback - generally, but also when you give the same feedback multiple times to someone. You find they nod, say it was fair, thank you for being direct, and then do the thing again the following week.</p>

        <p>It's something perplexing: you are specific in the feedback given, offer help, and mean it. You write the steps down, in order, in an email they could find again. You ask if there was anything in the way, and nothing changes.</p>

        <p>As the saying goes: you can lead a horse to water. But that hardly solves the problem, it just describes it. How many times must you take the horse to water? Should you fill up a bowl and raise it to its mouth? A PowerPoint on "drinking water"? Should you bring in a horse expert to assist?</p>

        <div class="plain-english-box">
          <h2>Quick jargon guide</h2>
          <ul>
            <li><strong>Righting reflex:</strong> the helper's urge to spot a problem and fix it for someone, usually by telling them what to do. Named by the motivational interviewing people.</li>
            <li><strong>Reactance:</strong> the push-back people feel when they sense their freedom to choose being taken away, e.g. tell someone they must, and part of them wants to prove they needn't.</li>
            <li><strong>Precontemplation:</strong> the first of the stages of change: the person does not think there is a problem, so advice is ignored.</li>
            <li><strong>Goal commitment:</strong> whether the person has actually signed up to the goal: goals only predict performance when commitment is there.</li>
            <li><strong>Skill/will:</strong> a coaching shorthand. Can they do it, and do they want to? </li>
            <li><strong>Feedback intervention:</strong> any deliberate act of telling someone how they're doing. </li>
          </ul>
        </div>

        <h2>The proverb</h2>

        <p>It is the oldest English proverb still in daily use, recorded as early as 1175 in the Old English Homilies:<sup><a href="https://www.kenreid.co.uk/blog/leading-a-horse-to-water.html#ref-1" class="cite-ref">[1]</a></sup></p>

        <blockquote>
          <p>Hwa is thet mei thet hors wettrien the him self nule drinken?</p>
          <cite>&mdash; <em>Old English Homilies</em>, c. 1175 (who can give water to the horse that will not drink of its own accord?)</cite>
        </blockquote>

        <p>John Heywood reshaped it into the version everyone knows by 1546. This is not a new problem among people.</p>

        <p>The drinking is not yours to do, and no amount of leading <i>better</i> or putting in more effort as a leader will make a horse drink. It can be deeply irritating when the horse is now standing in the river up to its knees, you're gesturing at the water, and it just stares at you.</p>

        <p>The version I like adds a third clause. Charlie "Tremendous" Jones, an American insurance-agency-executive-turned-motivational-speaker best known for the bestseller <em>Life Is Tremendous</em>, used to put it this way:</p>

        <blockquote>
          <p>You may not be able to make a horse drink, but you can put salt in his oats and make him thirsty.</p>
          <cite>&mdash; Charlie "Tremendous" Jones</cite>
        </blockquote>

        <p>You cannot make the horse drink, but you CAN make it thirsty, you can check the water is fit to drink, and you can find out whether the animal in front of you is a horse that isn't thirsty or a horse that is ill.</p>

        <figure>
          <a href="https://github.com/DrKenReid/DrKenReid.github.io/releases/download/photos-v1/184.png" class="img-lightbox portfolio-img" style="cursor: zoom-in;">
            <img src="https://www.kenreid.co.uk/img/photography/thumb/184.webp" alt="Two greylag geese walking in single file across a sunlit lawn, the front one glancing back" loading="lazy" width="400" height="266" srcset="https://www.kenreid.co.uk/img/photography/thumb/184.webp 400w, https://www.kenreid.co.uk/img/photography/thumb/184@2x.webp 800w, https://www.kenreid.co.uk/img/photography/hero/184.webp 1200w" sizes="(min-width: 800px) 760px, 100vw">
          </a>
          <figcaption class="figure-note">
            <span class="figure-copyright">&copy; Ken Reid. All rights reserved.</span>
            These are not horses, but it turns out I haven't taken many photos of horses, so here are some geese. No problems taking them to water.
          </figcaption>
        </figure>

        <h2>First, check the water</h2>

        <p>Before anything else: was the feedback actually constructive? In Kluger and DeNisi's 1996 meta-analysis, which pooled 607 effect sizes from feedback studies, feedback improved performance on average, but in over a third of cases it made performance worse.<sup><a href="https://www.kenreid.co.uk/blog/leading-a-horse-to-water.html#ref-2" class="cite-ref">[2]</a></sup> The feedback that hurt was the kind that pulled attention towards the self (who you are, how you compare) rather than the task (what to do differently, next time, at this step). No one wants to hear what is wrong with their character, but many are happy to hear how they can improve the technique, the strategy or the steps. </p>

        <p>It's always satisfying when a scientific study rings true. I know that I have this problem when I receive feedback: I feel attacked, if it's about my underlying drive or passion, but when I am given feedback on specific actions or tasks, I feel motivated. So, now I try to do the following when giving other people feedback: can they repeat back, in their own words, what they are meant to do differently, by when, and how they will know they've done it? If the answer to any of the three is a shrug, then I did not lead a horse to water, I led a horse to a description of water. "Be more proactive" is a high-level descriptor and nowhere near specific enough.</p>

        <h2>Second, check whether the horse is thirsty</h2>

        <p>The second question is whether the person thinks there is anything to fix. Prochaska and DiClemente's stages of change model, built on decades of watching people quit smoking and fail to, puts a stage before "thinking about it": precontemplation, where the person does not see a problem at all.<sup><a href="https://www.kenreid.co.uk/blog/leading-a-horse-to-water.html#ref-3" class="cite-ref">[3]</a></sup> Advice delivered to someone in precontemplation is like telling someone it will eventually rain in a city fifty miles away, at least to them. The smoking example also rings true for me: I remember begging my parents to stop smoking, even threatening to start myself if they didn't stop. Ultimately, a person needs to want to change a habit, otherwise any well-meaning, well-informed, kind and considered advice is utterly for naught. </p>

        <p>"Yeah, fair, thanks for saying" is what a person says when they agree and intend to act, and it is also what they say to make the meeting end while being polite. This means we can't really settle on that as the finale to the feedback conversation. Klein and colleagues' meta-analysis of goal commitment says: goals predict performance when people are committed to them, and barely at all when they are not, and commitment is not the same as having heard the goal read aloud.<sup><a href="https://www.kenreid.co.uk/blog/leading-a-horse-to-water.html#ref-4" class="cite-ref">[4]</a></sup></p>

        <p>Worse, pushing harder at this stage makes it worse. Miller and Rollnick, the motivational interviewing people, call the helper's urge to fix the "righting reflex," and their central finding, replicated across addiction, health and now management, is that when you argue for change, the other person starts arguing against it, and hearing themselves argue against it makes them believe it.<sup><a href="https://www.kenreid.co.uk/blog/leading-a-horse-to-water.html#ref-5" class="cite-ref">[5]</a></sup> Brehm named the underlying push-back in 1966: reactance, the instinct to reclaim a freedom someone is trying to take.<sup><a href="https://www.kenreid.co.uk/blog/leading-a-horse-to-water.html#ref-6" class="cite-ref">[6]</a></sup> You can't tell a horse it's thirsty and expect it to believe you.</p>

        <h2>Third, check whether the horse can drink</h2>

        <p>Someone agreeing and then not doing is what inspired me to write this, and it is usually read as a will problem when it is often a capability or environment problem. Landsberg's skill/will matrix is a two-by-two from a coaching book.<sup><a href="https://www.kenreid.co.uk/blog/leading-a-horse-to-water.html#ref-7" class="cite-ref">[7]</a></sup> Low skill needs teaching while low will needs reasoning, and low both needs direction. Getting these wrong is like trying to inspire a fish to walk.</p>

        <p>How to know if it's a "will" problem:</p>

        <ul>
          <li><strong>Have I seen them do it once?</strong> If I have never watched the person do the thing, even badly, I do not know whether they can. "Write clearer tickets" assumes they know what a clear ticket looks like. Show them one. Then watch them write one. </li>
          <li><strong>What is paying them to do the opposite?</strong> If they are incentivized or otherwise follow a protocol established by a higher authority or by tradition, they may ignore your feedback.</li>
          <li><strong>Is there room?</strong> Asking someone drowning in tickets to also improve how they write tickets is asking for the improvement to be ignored.</li>
        </ul>

        <figure>
          <a href="https://github.com/DrKenReid/DrKenReid.github.io/releases/download/photos-v1/51.png" class="img-lightbox portfolio-img" style="cursor: zoom-in;">
            <img src="https://www.kenreid.co.uk/img/photography/thumb/51.webp" alt="A mallard drake standing on a mossy bank at the edge of a pond, surrounded by branches" loading="lazy" width="400" height="266" srcset="https://www.kenreid.co.uk/img/photography/thumb/51.webp 400w, https://www.kenreid.co.uk/img/photography/thumb/51@2x.webp 800w, https://www.kenreid.co.uk/img/photography/hero/51.webp 1200w" sizes="(min-width: 800px) 760px, 100vw">
          </a>
          <figcaption class="figure-note">
            <span class="figure-copyright">&copy; Ken Reid. All rights reserved.</span>
          </figcaption>
        </figure>

        <h2>Salting the oats</h2>

        <p>Given a horse that is capable, not obviously ill, and not thirsty, here are some of my thoughts and learnings compiled together:</p>

        <ol>
          <li>Listen to them, don't assume. Ask them to say what they'll do, and write down what they said, not what you wanted them to say. A plan in someone else's words is often very different from what you think they know. Locke and Latham's goal-setting work has been saying this for forty years: participation in setting the goal raises commitment to it.<sup><a href="https://www.kenreid.co.uk/blog/leading-a-horse-to-water.html#ref-8" class="cite-ref">[8]</a></sup></li>
          <li>One behaviour, one fortnight, one checkpoint. "Be a better communicator" is useless. "Send the summary before the meeting, for the next three meetings, and we'll look at it on the 20th" is tangible, measurable and clear - the outcome is "becoming a better communicator" but the steps are clearly stated.</li>
          <li>If they don't see shortcomings they don't see a problem to fix. If the report is late and you fix it for them at eleven at night, the report is never late from their standpoint. </li>
          <li>Changing a person's behaviour is difficult. Changing a template, a checklist, a pairing session, a definition of done, is straightforward and easy.</li>
          <li>"This is the third time we've had this conversation. What's going on?" and let silence reign until an answer comes. Listening is important.</li>
          <li>"We agreed X by the 20th." A dated, agreed expectation turns the fourth conversation from an argument about memory into a paper trail.</li>
        </ol>

        <h2>How far to go</h2>

        <p>A failure of leadership is waiting too long. When behaviours become patterns that have been that way for years, you are fighting both the problem and established tradition. You can always try one more thing, one more framing, one more month. So the leader who doesn't decide in advance ends up deciding by exhaustion, which is late, and usually unkind to the team, to yourself, and to your colleague.</p>

        <p>Set the budget before the third conversation. What decides it is not how much you like the person (something I know we all struggle with). It's the cost of letting the issue stand:</p>

        <ul>
          <li><strong>You.</strong> The hours, obviously, but also the attention. A stuck person takes up a share of a leader's head that is wildly out of proportion to their share of the team.</li>
          <li><strong>The others.</strong> Every hour spent on the horse that won't is an hour taken from the horses that would.</li>
          <li><strong>The standard.</strong> What you tolerate for long enough becomes the definition of acceptable. And that can also spread: "Well, Jon takes 3 days on these tasks, why should I deprioritize it too?"</li>
          <li><strong>The person.</strong> Every month they spend in a role they are not going to grow into is a month they are not spending somewhere they might. Endless patience inhibits their growth, prevents them from achieving their goals (and possibly stymieing their future income, prospects, benefits, etc.).</li>
        </ul>

        <p>Then, how do you read which kind of stuck you're looking at? </p>

        <ol>
          <li><strong>They try, and fail.</strong> They attempt the thing, it comes out wrong, they attempt it again. This is a skill gap and it is - believe it or not - the good kind of stuck. Keep going, teach more concretely, lengthen the horizon. At least the horse is trying to drink!</li>
          <li><strong>They agree, and don't.</strong> Nod, thanks, nothing. This is will or environment, and the response is a single change of approach, followed, if nothing shifts, by the written expectation with a date on it.</li>
          <li><strong>They dispute the premise.</strong> "I don't think that's a problem." Either they're in precontemplation, or you are wrong. Stop advising and focus on listening. Then go and check with somebody who has no stake in it whether the water you're offering is actually water.</li>
        </ol>

        <h2>When to give up</h2>

        <p>Giving up on an approach is not giving up on a person. Most of what I've described above is giving up: on explanation, on the plan in your words, on absorbing the consequence. Surrendering is often surrendering the current strategy for a better one.</p>

        <p>Giving up on the person is very different. Informal feedback, then explicit written expectation, then a formal process, then parting. The leader's obligation at each rung is the same: the person must never be surprised. If someone learns at a formal meeting that their job is at risk and this is news to them, the leader failed. And if you're not their leader but are providing feedback, sometimes giving up is giving up your part in the process to someone else - their leader, their mentor, the person themself.</p>

        <p>I hate this part.</p>

        <p>I'd rather run the sixth conversation than the formal one, and I think it's something many people avoid out of feelings of not wanting to be the bad guy. Ultimately, you can only control that which is your own action, not the outcome. Some wise words by an idol of mine, a physically crippled man who escaped slavery and learned much about our control over our personal destinies:</p>

        <blockquote>
          <p>The things in our control are opinion, pursuit, desire, aversion, and, in a word, whatever are our own actions. The things not in our control are body, property, reputation, command, and, in one word, whatever are not our own actions.</p>
          <cite>&mdash; Epictetus, <em>Enchiridion</em></cite>
        </blockquote>

        <p>You can lead the horse to water, you can make sure the water is clean, and you can show it how to drink, but no matter how much you want to, out of compassion, kindness or care, there is a point where you have to accept that you are not the horse and you cannot force it to drink. The servant leadership test is whether the people you lead grow. If they are not growing with you after a fair run, the kind act may be to help them find where they will, perhaps a sister team, or through a friend you know from college. I personally believe leaders should help colleagues beyond their current role and company, and encourage growth, because paths can cross many times, so help them even if not from inside your own team or organization. And remember, this cannot be a surprise, this is only after a long period of trying and goal setting.</p>

        <h2>A flow chart</h2>

        

        <div class="faq-section">
          <h2>Common questions</h2>

          <details class="faq-item">
            <summary>Isn't "salting the oats" just manipulation?</summary>
            <p>The line I use: it's manipulation if they'd object to it on finding out. Everything in the list above can and should be said directly to the person ("I'm going to stop fixing this for you, so you can see when it goes wrong"). It's only manipulative if you don't tell them.</p>
          </details>

          <details class="faq-item">
            <summary>What if I'm the horse?</summary>
            <p>Ask yourself which of the three patterns you're in. If you're nodding to end meetings, you're in precontemplation and the honourable thing is to say so: "I don't think this is a problem, and here's why." Regardless of which part - have clear communication with your leader and follow the steps with them, or by yourself.</p>
          </details>

          <details class="faq-item">
            <summary>Does any of this work when they're senior to you?</summary>
            <p>Less of it. You can check the water (is your point specific, is it attached to something they care about), and you can ask rather than tell. You cannot set a date on a page for your boss, and you cannot stop absorbing their consequences without it costing you. Proceed with caution, but a good leader accepts constructive feedback from their reports.</p>
          </details>

          <details class="faq-item">
            <summary>How long is long enough?</summary>
            <p>Like any habit setting, longer than it takes to see a result: so long that it's the default and feels comfortable to everyone without slippage.</p>
          </details>
        </div>

        <h2 class="section-heading" id="references">References</h2>

        <ol class="references">
          <li id="ref-1">Old English Homilies, c. 1175. John Heywood, <em>A Dialogue Conteinyng the Nomber in Effect of All the Prouerbes in the Englishe Tongue</em> (1546). Discussed at <a href="https://www.phrases.org.uk/meanings/you-can-lead-a-horse-to-water.html" target="_blank" rel="noopener noreferrer">phrases.org.uk</a></li>
          <li id="ref-2">Kluger, A. N., &amp; DeNisi, A. (1996). The effects of feedback interventions on performance: A historical review, a meta-analysis, and a preliminary feedback intervention theory. <em>Psychological Bulletin</em>, 119(2), 254&ndash;284. 607 effect sizes; mean d = 0.41; over one third of interventions reduced performance.</li>
          <li id="ref-3">Prochaska, J. O., &amp; DiClemente, C. C. (1983). Stages and processes of self-change of smoking: Toward an integrative model of change. <em>Journal of Consulting and Clinical Psychology</em>, 51(3), 390&ndash;395.</li>
          <li id="ref-4">Klein, H. J., Wesson, M. J., Hollenbeck, J. R., &amp; Alge, B. J. (1999). Goal commitment and the goal-setting process: Conceptual clarification and empirical synthesis. <em>Journal of Applied Psychology</em>, 84(6), 885&ndash;896. Meta-analysis of 83 samples. <a href="https://pubmed.ncbi.nlm.nih.gov/10639908/" target="_blank" rel="noopener noreferrer">pubmed.ncbi.nlm.nih.gov</a></li>
          <li id="ref-5">Miller, W. R., &amp; Rollnick, S. (2013). <em>Motivational Interviewing: Helping People Change</em> (3rd ed.). Guilford Press. The righting reflex, ambivalence, and why arguing for change produces arguments against it. Summary at <a href="https://www.ncbi.nlm.nih.gov/books/NBK571068/" target="_blank" rel="noopener noreferrer">ncbi.nlm.nih.gov</a></li>
          <li id="ref-6">Brehm, J. W. (1966). <em>A Theory of Psychological Reactance</em>. Academic Press. Reviewed fifty years on in Steindl et al. (2015), <em>Zeitschrift f&uuml;r Psychologie</em>, 223(4), 205&ndash;214.</li>
          <li id="ref-7">Landsberg, M. (1996). <em>The Tao of Coaching</em>. HarperCollins. Source of the skill/will matrix (direct, guide, excite, delegate).</li>
          <li id="ref-8">Locke, E. A., &amp; Latham, G. P. (2002). Building a practically useful theory of goal setting and task motivation: A 35-year odyssey. <em>American Psychologist</em>, 57(9), 705&ndash;717.</li>
        </ol>

        

      ]]></content:encoded>
    </item>
    <item>
      <title>Making This Website Accessible</title>
      <link>https://www.kenreid.co.uk/blog/making-this-website-accessible.html</link>
      <guid>https://www.kenreid.co.uk/blog/making-this-website-accessible.html</guid>
      <pubDate>Sun, 30 Aug 2026 00:00:00 +0000</pubDate>
      <description>Thirty small habits keep this site accessible: a skip link, screen-reader notes on new-tab links, reduced-motion fallbacks, and an audit that catches the many that I forget along the way.</description>
      <category>technology</category>
      <content:encoded><![CDATA[

 <p>I have <a href="https://www.kenreid.co.uk/blog/accessibility-first-product-design-data-science.html">banged on before</a> about accessibility working best as a starting constraint rather than a retrofit. This site is where I get to practise that habit continually: my core philosophy is that if something is inaccessible to some it may as well be inaccessible to all. I make sure of this with a pile of small habits, and an audit script that catches the bits I inevitably miss. Part of the series on <a href="https://www.kenreid.co.uk/series-how-this-site-is-built.html">how this site is built</a>.</p>

 <div class="plain-english-box">
 <h2>Quick jargon guide</h2>
 <ul>
 <li><strong>Screen reader:</strong> software that reads a page aloud (or to a braille display) for blind and low-vision users, navigating by headings, links, and landmarks rather than by looking.</li>
 <li><strong>WCAG:</strong> the Web Content Accessibility Guidelines, the standard everyone measures against. Its techniques have catalogue numbers, like items at IKEA.</li>
 <li><strong>ARIA attributes:</strong> HTML attributes (<code>aria-pressed</code>, <code>aria-label</code>, <code>aria-live</code>) that tell assistive technology what a widget is and what state it's in when the visuals alone can't.</li>
 <li><strong>sr-only:</strong> a CSS class that hides text visually but leaves it for screen readers. The inverse of decoration.</li>
 <li><strong>Skip link:</strong> a link at the very top of the page, visible only when focused, that jumps keyboard users past the navigation to the content.</li>
 <li><strong>Reduced motion:</strong> an operating-system setting (<code>prefers-reduced-motion</code>) that asks websites to calm down with the animations. Vestibular disorders make this a health feature, not a taste.</li>
 <li><strong>Focus trap:</strong> keeping keyboard focus inside an open dialog so Tab doesn't wander off into the page behind it.</li>
 </ul>
 </div>

 <h2>The keyboard path</h2>

 <p>The first element injected into every page is what is known as a skip link. Press Tab on any page here and "Skip to main content" appears before anything else. In short, this allows you to press Enter and get past any navigation fluff at the top of the page. It's invisible to mouse users, and is an old trick that is often forgotten in 'modern' websites.</p>

 <figure class="kr-figure--solo">
  <img src="https://www.kenreid.co.uk/blog/img/a11y/skip-link.webp" alt="The site header with a Skip to main content button overlaid on the navigation, outlined by a red focus ring" loading="lazy" width="1520" height="214" class="kr-figure-img">
  <figcaption class="figure-note">Press Tab anywhere on the site and this is the first stop. Mouse users never see it.</figcaption>
 </figure>

 <p>The Ctrl+K <a href="https://www.kenreid.co.uk/blog/ctrl-k-for-a-static-site.html">command palette</a> got the full dialog treatment: focus moves into the input when it opens, Tab is trapped (it moves the selection instead of escaping the dialog), Escape closes it, and results are given to screen readers through an <code>aria-live</code> region. The filter buttons on the blog and gallery use <code>aria-pressed</code>, so "Photography, toggle button, pressed" comes through instead of a mystery link.</p>

 <figure class="kr-figure--solo">
  <img src="https://www.kenreid.co.uk/blog/img/a11y/command-palette.webp" alt="The Ctrl+K command palette open over a dimmed homepage, with the query accessib typed, six post results listed, the top one highlighted, and footer hints reading navigate, open, esc close" loading="lazy" width="1520" height="1064" class="kr-figure-img">
  <figcaption class="figure-note">The palette moves focus into the input, traps Tab, closes on Escape, and announces results through an aria-live region.</figcaption>
 </figure>

 <figure class="kr-figure--solo">
  <img src="https://www.kenreid.co.uk/blog/img/a11y/tag-filters.webp" alt="The blog's Filter and Sort buttons above the Topic row: All (68), technology, ai and personal as outlined buttons, the photography tag solid red to show its pressed state, and a +9 more button" loading="lazy" width="1520" height="248" class="kr-figure-img">
  <figcaption class="figure-note">The pressed filter reads as "photography, toggle button, pressed": the visual state and the aria-pressed state are the same fact.</figcaption>
 </figure>

 <p>The theme toggle swaps its label between "Switch to light mode" and "Switch to dark mode", so the button always says what it will do, not what it did. It has no <code>aria-pressed</code>, on purpose: a label that changes plus a pressed state gets read out as "Switch to light mode, toggle button, pressed", which leaves you to work out whether pressed means light or dark.</p>

 <h2>The screen reader path</h2>

 <p>Links that open a new tab get a screen-reader-only annotation, WCAG technique G201, applied automatically:</p>

<pre><code class="language-javascript">// Appends screen-reader-only "(opens in new tab)" to target="_blank" links
var span = document.createElement('span');
span.className = 'sr-only';
span.textContent = ' (opens in new tab)';
link.appendChild(span);</code></pre>

 <p>A MutationObserver reapplies it to links injected later (related posts, footers), because on this site half the content arrives after first paint. For a screen reader user, a link that teleports you into a new tab with no warning is awful.</p>

 <p>Images carry real alternative text - I imagine not having it feels almost painful for screen-reader users, they likely feel they're missing out on important content and don't know what it is, like missing pages in a book. I aim for a description, not caption: the photo of my dog is "A picture of my puppy, Brutus", and the chart images in data posts describe the actual bars and numbers, so the information is actually useful, instead of "a photo" or "the results shown as an image". The interactive posts give every canvas <code>role="img"</code> and an <code>aria-label</code> saying what the simulation shows. A label is a thin substitute for "here is a live algorithm race you cannot see", so I'm open to suggestions on how to make it a better experience.</p>

 <h2>The vestibular path</h2>

 <p>Every script that moves something checks <code>prefers-reduced-motion</code> (eight of them at the last count), and the stylesheet has 28 media blocks for it. The section headings that rise into place as you scroll are pure CSS (<code>animation-timeline: view()</code>), and with reduced motion on they just appear, already visible, as does the script fallback for browsers without it. The homepage stats display their final number instead of counting up. The interactive demos mount paused, the carousels start paused, the hover sketches on the blog cards never load, the hero's evolving headline is drawn as plain type, and the theme switch cross-fades instead of washing across the page from the button. The site has to be complete with the animation subtracted, and only then is the animation allowed.</p>

 <h2>Checkpoints</h2>

 <p>Habits decay, so the <a href="https://www.kenreid.co.uk/blog/my-website-has-a-test-suite.html">audit script</a> checks every page for the a11y basics (alt text, heading structure, link health, metadata) on every push, a contrast test checks every text colour and the focus ring against WCAG AA in both themes, and a headless browser presses Tab for real to make sure the focus ring shows up, measures the contrast on the rendered pages, and checks the demos work from a keyboard without flooding a screen reader. CI fails loudly when a new post shows how bad my memory is at maintaining these standards without a checklist. I love automating stuff, I'd rather put in extra effort early to save effort later.</p>

 <figure class="kr-figure--solo">
  <img src="https://www.kenreid.co.uk/blog/img/a11y/audit-output.webp" alt="Terminal output of the audit script: four INFO lines about long meta descriptions, then a summary reading 86 pages audited, 0 errors, 0 warnings, 29 info, with the zeroes in green" loading="lazy" width="1520" height="449" class="kr-figure-img">
  <figcaption class="figure-note">The audit summary on a clean tree the week this post went up. It reads 95 pages audited now, still with 0 errors and 0 warnings. CI runs the same script on every push and fails the build on any error or warning.</figcaption>
 </figure>

 <p>I'd love to hear from an actual screen reader user. If you use assistive tech and something here fights you, the <a href="https://www.kenreid.co.uk/contact.html">contact page</a> works, and I fix reported things fast.</p>

 <div class="faq-section">
 <h2>Common questions</h2>

 <details class="faq-item">
 <summary>It's just a blog why do you put in so much effort?</summary>
 <p>Reverse the question: what justifies publishing a public page some readers can't use? The effort is also smaller than it looks. Nearly everything above was under an hour of work, done once, and enforced by automation afterwards. The expensive version of accessibility is the retrofit. </p>
 </details>

 <details class="faq-item">
 <summary>Does dark mode count as accessibility?</summary>
 <p>For me, yes. I get headaches and my eyes burn when I look at bright screens. So <a href="https://www.kenreid.co.uk/blog/dark-mode-that-doesnt-flash.html">avoiding the white flash</a> matters most for people like me. For everyone else it's comfort. </p>
 </details>
 </div>

        

 ]]></content:encoded>
    </item>
    <item>
      <title>The Availability Heuristic Runs the News</title>
      <link>https://www.kenreid.co.uk/blog/the-availability-heuristic-runs-the-news.html</link>
      <guid>https://www.kenreid.co.uk/blog/the-availability-heuristic-runs-the-news.html</guid>
      <pubDate>Sun, 30 Aug 2026 00:00:00 +0000</pubDate>
      <description>We judge likelihood by how easily examples come to mind, so we fear the vivid and rare and shrug at the common and deadly. Why the news makes us afraid of the wrong things.</description>
      <category>philosophy</category>
      <category>science</category>
      <content:encoded><![CDATA[

 <p>The news is a list of the least representative things that happened yesterday, which you might think - great, that's interesting! The problem is, our minds calibrate to work out how dangerous the world is with this information. Quick: which is more dangerous, flying or driving? Sharks or vending machines? Terrorism or heart disease? If your gut said "flying, sharks, terrorism", your gut is confidently and dramatically wrong.</p>

 <p>You judge how likely a thing is by how <em>easily</em> examples come to mind. The examples that come easily are the vivid ones, the ones the news showed you. That, in a nutshell, is the availability heuristic, and it runs your sense of risk, your politics, and a fair amount of your fear.</p>

 <div class="plain-english-box">
 <h2>Quick jargon guide</h2>
 <ul>
 <li><strong>Availability heuristic:</strong> judging how likely something is by how easily examples spring to mind.</li>
 <li><strong>Base rate:</strong> how often a thing actually happens, as opposed to how often you hear about it.</li>
 <li><strong>Denominator:</strong> the "out of how many" under every scary number; a headline usually reports the numerator and skips this part.</li>
 <li><strong>Newsworthiness filter:</strong> news reports the unusual by definition, so what reaches you is selected against being representative.</li>
 <li><strong>Doomscrolling:</strong> an infinite feed of the world's most vivid catastrophes, which your gut reads as a frequency table.</li>
 </ul>
 </div>

 <h2>The shortcut</h2>

 <p>The availability heuristic, named by Tversky and Kahneman, is the brain's habit of estimating how frequent or likely something is by how readily instances spring to mind<sup><a href="https://www.kenreid.co.uk/blog/the-availability-heuristic-runs-the-news.html#ref-1" class="cite-ref">[1]</a></sup>. Most of the time it's a decent shortcut: common things usually <em>are</em> easier to recall than rare ones, so "easy to remember" tracks "actually frequent" well enough to get you through a Tuesday.</p>

 <p>It fails when ease of recall is driven by something <em>other</em> than frequency. In the modern world that is constant: vividness, emotional intensity and media coverage all make an event easier to recall without making it any more common.</p>

 <p>So the events that dominate our sense of risk are the rare, dramatic, well-covered ones. Plane crashes get wall-to-wall coverage, and car crashes, which kill vastly more people, are too routine to report, so flying <em>feels</em> more dangerous while being far safer per mile.</p>

 <h2>The risks with no pictures</h2>

 <p>Shark attacks get a summer of headlines out of a handful of incidents, while the far deadlier risks (heart disease, the drive to the beach) produce no images at all. Terrorism reshapes whole societies' fears and budgets while killing a tiny fraction of the number who die of the mundane, unphotogenic causes nobody panics about. That last one makes me angry.</p>

 <p>The mismatch between what we fear and what actually harms us is, in large part, the availability heuristic reading the news as if it were a frequency table<sup><a href="https://www.kenreid.co.uk/blog/the-availability-heuristic-runs-the-news.html#ref-2" class="cite-ref">[2]</a></sup>.</p>

 <h2>The news is an availability machine</h2>

 <p>The news isn't lying, which is the trap. News reports the <em>un</em>usual by definition: "plane lands safely" is not a story, "plane crashes" is. That newsworthiness filter guarantees that what reaches you is the rare and dramatic rather than the common and deadly.</p>

 <figure class="kr-figure--solo">
  <img src="https://www.kenreid.co.uk/img/photography/thumb/89.webp" alt="A city stretching to the hills under a lid of dark storm clouds" loading="lazy" class="kr-figure-img" width="400" height="265" srcset="https://www.kenreid.co.uk/img/photography/thumb/89.webp 400w, https://www.kenreid.co.uk/img/photography/thumb/89@2x.webp 800w, https://www.kenreid.co.uk/img/photography/hero/89.webp 1200w" sizes="(min-width: 800px) 760px, 100vw">
  <figcaption class="figure-note"><span class="figure-copyright">&copy; Ken Reid. All rights reserved.</span></figcaption>
 </figure>

 <p>So the news stocks your memory with the vivid, atypical events that the availability heuristic will then treat as frequent ones. You get a mental sample of the world selected for drama and against representativeness, and your sense of risk is built on that sample.</p>

 <p>This is a cousin of survivorship bias, and of the habit of analysing whatever data happened to be lying around: in all of them, a filtered sample gets mistaken for the whole picture.</p>

 <p>Then there is doomscrolling. An infinite feed that tailors towards what you enjoy - the algorithm literally learns based on how quickly you scroll, or don't, from one item to the next, whether you comment, react, share. So when you show an interest in, well, interesting things that are, say, unusual, such as landslides, violence, politics, terrorism videos, whatever - you begin to see MORE of that because the algorithm learns what you interact with. Therefore, you start to think "this is how things are".</p>

 <p>The same wiring that helped an ancestor remember which mushroom caused a friend to die, or where there were dangerous animals, is now allowing you to identify dangerous patterns in a carefully crafted information feed, fed to you from your phone.</p>

 <h2>What to do about a bias you can't turn off</h2>

 <p>You can't just "stop" this by knowing it exists, but you can install a correction: <strong>when you notice fear, or a judgement about risk, ask where the example came from.</strong> Did this feel likely because you have base-rate data, or because a headline handed you a picture? That one question separates "this is common" from "this is merely memorable".</p>

 <h2>The boring denominators</h2>

 <p>The deeper practice is going after <em>base rates</em>, the boring denominators, instead of anecdotes. How many flights per crash? How many people drive, versus how many die driving? What's the trend, rather than the latest incident?</p>

 <p>Numbers are the antidote because they're unmoved by vividness. A statistic does not care how frightening your mental image is (notice how often journalists say "thousands" without the "out of millions"). So the frightening image is your cue to go and find the statistic.</p>

 <p>Your intuition has been fed a curated diet of the dramatic, so when it hands you a confident sense of how risky the world is, treat the confidence as a symptom rather than a measurement.</p>

 <p>Next time a headline frightens you, go and find the denominator. It's usually two searches away, and nowhere near as interesting as the headline.</p>

 <div class="faq-section">
 <h2>Common questions</h2>

 <details class="faq-item">
 <summary>Doesn't this just tell people to ignore the news and real dangers?</summary>
 <p>Journalists judge success by number of viewers and interactions. They are incentivised to make news that is clickbait, sensationalised, or blatantly pandering to specific crowds. "The news" is real-world information filtered through groups of people incentivised to misbehave. I recommend at least trying various news outlets, but preferably, get your news from smaller agencies, and be deeply discerning.</p>
 </details>

 <details class="faq-item">
 <summary>If the world is safer than it feels, why does everyone feel it's getting worse?</summary>
 <p>There are a half-dozen upcoming apocalypses that are real, including climate change, antibiotic resistance, and world war. Use your time and anxiety to better the world and our chances of preventing those things, don't fret over inflated figures from pot-stirring articles complaining about immigration or gang violence - it's clickbait and it is seeding hatred against our fellow humans.</p>
 </details>

 <details class="faq-item">
 <summary>Isn't it a bit privileged to tell people their fears are irrational?</summary>
 <p>It would be, if the claim were "your fears are stupid". The heuristic is a universal shortcut with a sensible origin, not a personal failing, and plenty of the fears it produces are justified. The argument is only that ease of recall and true frequency come apart in a media-saturated world, so it pays to check which one a given fear is tracking, especially for the high-stakes calls (what to worry about, how to vote, what you will let reshape a society). Respecting people's fears and helping them aim those fears well are compatible.</p>
 </details>
 </div>

 <h2 class="section-heading" id="references">References</h2>
 <ol class="references">
          <li id="ref-1">
            Tversky, A., &amp; Kahneman, D. (1973). Availability: A heuristic for judging frequency and probability. <em>Cognitive Psychology</em>, 5(2), 207&ndash;232. <a href="https://doi.org/10.1016/0010-0285(73)90033-9" target="_blank" rel="noopener">https://doi.org/10.1016/0010-0285(73)90033-9</a>
          </li>
          <li id="ref-2">
            Slovic, P. (1987). Perception of risk. <em>Science</em>, 236(4799), 280&ndash;285. <a href="https://doi.org/10.1126/science.3563507" target="_blank" rel="noopener">https://doi.org/10.1126/science.3563507</a>
          </li>
        </ol>

        
 ]]></content:encoded>
    </item>
    <item>
      <title>Confirmation Bias Is the Mother Bias</title>
      <link>https://www.kenreid.co.uk/blog/confirmation-bias-is-the-mother-bias.html</link>
      <guid>https://www.kenreid.co.uk/blog/confirmation-bias-is-the-mother-bias.html</guid>
      <pubDate>Fri, 28 Aug 2026 00:00:00 +0000</pubDate>
      <description>Confirmation bias is the bias under the biases. Why it&#x27;s the hardest to fix, why smart people are worse, and there might be something that can help us.</description>
      <category>philosophy</category>
      <category>science</category>
      <content:encoded><![CDATA[

 <p>Nobody googles "reasons I am wrong about this". We google "reasons I am right about this", and we smugly nod at how wise we are.</p>

 <p>There are dozens of named cognitive biases, and this is the start of a series on them - I find them fascinating and I'm using this series as a means to learn about some psychology 101, and share what I've learned. One such bias sits under the others and is something of a basis for others. Confirmation bias is the tendency to seek, notice, remember and believe evidence that supports what we already think, and to ignore, discount or forget evidence that doesn't. It's how your uncle can keep bringing up the same political arguments at Thanksgiving dinner, forgetting what you've already said that clearly counters his argument - he won't remember, because the information you provide is irrelevant to him.</p>

 <div class="plain-english-box">
 <h2>Quick jargon guide</h2>
 <ul>
 <li><strong>Confirmation bias:</strong> seeking out, noticing, remembering and believing whatever supports what you already think, while the rest slides past.</li>
 <li><strong>Motivated reasoning:</strong> reasoning that works backwards from the conclusion you want, while feeling from the inside like fair weighing.</li>
 <li><strong>Steelmanning:</strong> stating the strongest version of the case against you, so well that the people who hold it would sign off on your summary.</li>
 <li><strong>Motivated numeracy:</strong> the finding that better reasoning skills can <em>increase</em> polarisation on charged questions, because the skill goes to work for the side you already picked.</li>
 </ul>
 </div>

 <h2>Evidence</h2>

 <p>The comforting model of the mind is a courtroom: you gather evidence, weigh it impartially, and reach a verdict. The actual mind runs the process backwards most of the time. You start with the verdict (the belief you already hold, or want to hold) and then work as its defence lawyer, scouring for supporting evidence and cross-examining anything inconsistent into silence. </p>

 <p>You search for confirming information. You read ambiguous evidence as supporting your side. You remember the hits and forget the misses. You let confirming evidence in over a doorstep and make disconfirming evidence climb a wall<sup><a href="https://www.kenreid.co.uk/blog/confirmation-bias-is-the-mother-bias.html#ref-1" class="cite-ref">[1]</a></sup>. </p>

 <h2>Setting the stage</h2>

 <p>I want to put two scenes down here, because I am going to keep returning to them throughout the series (or try to, if I can bend the examples enough each time!). Every bias in this run is easier to see when it happens to the same people in the same rooms, so: meet the dinner table and the dashboard. They will be back!</p>

 <aside class="kr-scene">
  <p class="kr-scene-label">The dinner table</p>
  <p>My uncle has decided the earth is flat. He arrives at dinner with a video of a coastline where you can supposedly see a lighthouse the curve should have hidden, and a man saying "they never show you the curve." He found it in seconds, because he searched for "proof flat earth real". I countered it, again. You can lie down, watch the sun set, then stand up and watch it set a second time, which wouldn't work on a flat earth. All the other planets can be observed to be spherical. Shadows over time show the Earth is round. None of it sticks: the counter-evidence is immediately dismissed. I run the same search he does, I consider it and work through from his perspective back to mine, rather than searching "proof earth is not flat", and that makes me understand his approach more, and reinforces my position as a rigorous argument, instead of parroting what I previously read.</p>
  <figure class="kr-figure--solo">
   <img src="https://www.kenreid.co.uk/img/photography/thumb/211.webp" alt="An older man in glasses and a tie at a busy restaurant dinner table, mid-conversation, wine glasses and flowers in front of him" loading="lazy" class="kr-figure-img" width="400" height="266" srcset="https://www.kenreid.co.uk/img/photography/thumb/211.webp 400w, https://www.kenreid.co.uk/img/photography/thumb/211@2x.webp 800w, https://www.kenreid.co.uk/img/photography/hero/211.webp 1200w" sizes="(min-width: 800px) 760px, 100vw">
   <figcaption class="figure-note"><span class="figure-copyright">&copy; Ken Reid. All rights reserved.</span></figcaption>
  </figure>
  <p class="kr-scene-note">The uncle searched for evidence to prove his position, assuming he was already correct, not showing curiosity and willingness to consider other opinions. Classic confirmation bias.</p>
 </aside>

 <aside class="kr-scene">
  <p class="kr-scene-label">The neighbour</p>
  <p>I knew before even moving into my new place that my neighbours wouldn't like me. And, in my first week here, that man across the road clearly didn't like me. He doesn't wave, and then, when he does wave, it's begrudging, only after I do. He put his bins out later than me, but then after he saw I did it earlier, he's almost doing it the night before to beat me. I send him a Christmas card but he didn't send one to me. He did sign for a parcel for me when I wasn't home, but it just feels like he's doing it to have one over me, seem to be the nicer neighbour - so fake. I knew it before I even met my neighbour: I knew he wouldn't like me, and every interaction just proves it. </p>
  <figure class="kr-figure--solo">
   <img src="https://www.kenreid.co.uk/img/photography/thumb/306.webp" alt="A concrete tower block shot from below against a clear blue sky, rows of windows receding upward" loading="lazy" class="kr-figure-img" width="400" height="266" srcset="https://www.kenreid.co.uk/img/photography/thumb/306.webp 400w, https://www.kenreid.co.uk/img/photography/thumb/306@2x.webp 800w, https://www.kenreid.co.uk/img/photography/hero/306.webp 1200w" sizes="(min-width: 800px) 760px, 100vw">
   <figcaption class="figure-note"><span class="figure-copyright">&copy; Ken Reid. All rights reserved.</span></figcaption>
  </figure>
  <p class="kr-scene-note">Here, the person new to the neighbourhood already decided how the relationships with their neighbours would be, and took any evidence (even the neighbour being nice) as evidence for the neighbour hating them.</p>
 </aside>

 <h2>Why intelligence hinders you</h2>

 <p>Being clever, educated or numerate does not protect you (despite what most of us tell ourselves), and can make things <em>worse</em>. A smarter person is a better lawyer: quicker at generating persuasive arguments for what they already believe, better at spotting the flaw in inconvenient evidence, more fluent at explaining away the anomaly. It's funny how we assume intelligent people are wiser. There's a reason wisdom and intelligence are separate ability scores in pathfinder and dungeons and dragons: intelligence is knowing that a tomato is a fruit, wisdom is not adding it to your cereal.</p>

 <p>Studies find that greater numeracy and reasoning ability can <em>increase</em> polarisation on politically charged questions, because people put their cognitive horsepower to work for their existing side rather than for the truth<sup><a href="https://www.kenreid.co.uk/blog/confirmation-bias-is-the-mother-bias.html#ref-2" class="cite-ref">[2]</a></sup>. The ignorant are overconfident, fine, we all knew that. The capable are better at rationalising, which is not a great thing for society.</p>

 <h2>The one habit that helps (apparently)</h2>

 <p>Willpower does nothing here. "Be less biased" is an instruction on the level of "be taller". Confirmation bias runs below awareness, so telling yourself to be fair-minded mostly produces a more confident lawyer. The thing that helps is structural: <strong>go and find the strongest version of the opposing case.</strong></p>

 <p>Before settling a belief that matters, hunt down the best argument <em>against</em> it, from the cleverest people who disagree, and try to state their case so well they would sign off on your summary. That is steelmanning, and it is the discipline behind <a href="https://www.kenreid.co.uk/blog/how-to-write-a-blog.html">how I try to write</a> (and mostly fail at).</p>

 <p>Ask "what would change my mind?" and get worried if the answer is "nothing". Watch for the flinch, too. That little urge to wave a piece of evidence away is strong and itself deeply biased - you're doing that with your "tribe", your "side" and your "argument" in mind, not actually fairly comparing evidence. Your defence lawyer has found something and would like it kept out of the record.</p>

 <h2>But, even with that...</h2>

 <p>It won't make you unbiased. Nothing does. But - it will insert a step where you point your intelligence towards something more than building walls.</p>

 <p>You cannot trust your sense of having weighed the evidence fairly. It is a horrible fact to know about yourself and we're kind of stuck with that fact. What's worse, knowing this doesn't solve the problem. It's like recognizing you having a proclivity for a certain flavor of ice cream doesn't make you like all of them equally suddenly. But maybe it can help you try new flavors, if you take that next step. This metaphor is a bit stretched: what I mean is, try thinking of other perspectives thoroughly. Intellectual empathy; emotional, too. </p>

 <figure class="kr-figure--solo">
  <img src="https://www.kenreid.co.uk/img/photography/thumb/66.webp" alt="Stainless steel trays of gelato in a shop counter, chocolate chip, caramel swirl and nut flavours side by side" loading="lazy" class="kr-figure-img" width="400" height="265" srcset="https://www.kenreid.co.uk/img/photography/thumb/66.webp 400w, https://www.kenreid.co.uk/img/photography/thumb/66@2x.webp 800w, https://www.kenreid.co.uk/img/photography/hero/66.webp 1200w" sizes="(min-width: 800px) 760px, 100vw">
  <figcaption class="figure-note"><span class="figure-copyright">&copy; Ken Reid. All rights reserved.</span></figcaption>
 </figure>

 <p>Go and read the <a href="https://doi.org/10.1037/1089-2680.2.2.175" target="_blank" rel="noopener">Nickerson review</a>. It came out in 1998.</p>

 <div class="faq-section">
 <h2>Common questions</h2>

 <details class="faq-item">
 <summary>If everyone has confirmation bias, isn't objective truth hopeless?</summary>
 <p>No. Individuals are badly biased, but well-designed <em>systems</em> can be much better than any individual in them. Science works, imperfectly, because it institutionalises disconfirmation: peer review, replication, adversarial scrutiny, predictions registered. The fix for a bias you cannot remove from a single head is to build processes where other people's confirmation biases point in opposite directions and cancel it out, somewhat.</p>
 </details>

 <details class="faq-item">
 <summary>How is confirmation bias different from just having strong opinions?</summary>
 <p>Having a strong opinion is fine and often justified. Confirmation bias is about how you <em>maintain</em> it: protecting it by filtering evidence instead of testing it. What gives it away is not the strength of the belief but how the belief behaves when challenged. A well-held strong opinion can name what would change it and engages with the best counterarguments. A bias-protected one waves disconfirming evidence off without looking at it and never seems to update about anything, ever. </p>
 </details>

 <details class="faq-item">
 <summary>Doesn't steelmanning the other side just make me vulnerable to bad ideas?</summary>
 <p>I think that a belief challenged is a belief trained, like lifting weights. It hurts, and is tiring, but it is the only way to improve. If you argument is so weak that it can't stand up to alternative view points, why are you so keen to hold onto it? </p>
 </details>
 </div>

 <h2 class="section-heading" id="references">References</h2>
 <ol class="references">
          <li id="ref-1">
            Nickerson, R. S. (1998). Confirmation bias: A ubiquitous phenomenon in many guises. <em>Review of General Psychology</em>, 2(2), 175&ndash;220. <a href="https://doi.org/10.1037/1089-2680.2.2.175" target="_blank" rel="noopener">https://doi.org/10.1037/1089-2680.2.2.175</a>
          </li>
          <li id="ref-2">
            Kahan, D. M., et al. (2017). Motivated numeracy and enlightened self-government. <em>Behavioural Public Policy</em>, 1(1), 54&ndash;86. <a href="https://doi.org/10.1017/bpp.2016.2" target="_blank" rel="noopener">https://doi.org/10.1017/bpp.2016.2</a>
          </li>
        </ol>

        
 ]]></content:encoded>
    </item>
    <item>
      <title>Tabu Search, Live</title>
      <link>https://www.kenreid.co.uk/blog/tabu-search-live.html</link>
      <guid>https://www.kenreid.co.uk/blog/tabu-search-live.html</guid>
      <pubDate>Wed, 26 Aug 2026 00:00:00 +0000</pubDate>
      <description>An interactive tabu search in your browser: watch a search with a memory solve N-Queens live, ban its own best moves, and escape the traps that stop every other method in this series.</description>
      <category>ai</category>
      <content:encoded><![CDATA[<p><em>This post includes an interactive demo that runs live in the browser. <a href="https://www.kenreid.co.uk/blog/tabu-search-live.html">View it on the site</a> to play with it.</em></p>

 <p>Eight posts in, every escape from a trap in this series has been some flavour of randomness:</p>
 <ul>
 <li><a href="https://www.kenreid.co.uk/blog/simulated-annealing-live.html">simulated annealing</a> gambles: while it's hot it accepts bad moves at random and hopes for betterment</li>
 <li><a href="https://www.kenreid.co.uk/blog/ant-colony-live.html">the ant colony</a> wanders its way out</li>
 <li><a href="https://www.kenreid.co.uk/blog/evolution-live.html">the genetic algorithm</a> and its <a href="https://www.kenreid.co.uk/blog/genetic-programming-live.html">program-breeding cousin</a> mutate their way out</li>
 <li><a href="https://www.kenreid.co.uk/blog/particle-swarm-live.html">the swarm</a> jitters</li>
 <li><a href="https://www.kenreid.co.uk/blog/cma-es-live.html">CMA-ES</a> and <a href="https://www.kenreid.co.uk/blog/differential-evolution-live.html">differential evolution</a> sample their way out of trouble</li>
 <li>even <a href="https://www.kenreid.co.uk/blog/variable-neighbourhood-search-live.html">variable neighbourhood search</a>, the most systematic escape artist so far, opens every escape with a random shake</li>
 </ul>

 <p>Tabu search is the odd one out, escaping traps by <em>remembering</em> them: it keeps a short list of places it has just been and refuses to go back, even when going back looks like the best move on the board. </p>

 

 <p>Fred Glover sketched the idea in 1986<sup><a href="https://www.kenreid.co.uk/blog/tabu-search-live.html#ref-1" class="cite-ref">[1]</a></sup> and built it out properly in 1989<sup><a href="https://www.kenreid.co.uk/blog/tabu-search-live.html#ref-2" class="cite-ref">[2]</a></sup>. The core of it is simple: run a greedy search, but make the recent past forbidden. That one addition turns the dumbest search strategy going into something that can walk out of a local optimum, cross a plateau, and keep improving. It needs no temperature and no coin to flip, which makes it something of an oddity in this series.</p>

 <div class="plain-english-box">
 <h2>Quick jargon guide</h2>
 <ul>
 <li><strong>Tabu search:</strong> a greedy search that keeps a short-term memory of recent moves and forbids undoing them, so it can climb out of traps instead of circling back in.</li>
 <li><strong>Tabu list:</strong> the ban list itself: the attributes of recent moves (in our demo, squares a queen just vacated) that the search is not allowed to revisit yet.</li>
 <li><strong>Tenure:</strong> how many iterations an entry stays on the tabu list before it expires.</li>
 <li><strong>Aspiration criterion:</strong> the escape clause: a tabu move is allowed anyway if it would produce the best solution ever seen. Bans are for moves that merely look good.</li>
 <li><strong>Cycling:</strong> the failure tabu search exists to prevent: a memoryless search bouncing between the same few solutions forever because each looks like the best neighbour of the other.</li>
 <li><strong>Steepest descent:</strong> evaluate every neighbour of the current solution and take the best one. Fast, greedy, and helpless the moment no neighbour improves.</li>
 <li><strong>Intensification vs diversification:</strong> Glover's tug-of-war: search hard near the good solutions you have found (intensify), versus force yourself somewhere you have never been (diversify). Recency memory the key here.</li>
 </ul>
 </div>

 <h2>Greed with amnesia</h2>

 <p>Steepest descent is the greediest search there is: look at every neighbour of your current solution, take the best one, repeat. It's fast, it's simple, and it's doomed: sooner or later it reaches a solution where no neighbour is better, and there it sits. I covered that failure in the annealing post, where the cure was randomness on a cooling schedule.</p>

 <p>Allow sideways moves (neighbours that are no worse, a common tweak for crossing flat regions) and the search steps from board A to board B, surveys the neighbourhood, and concludes that the best move from B is... back to A.</p>

 <p>So it goes back, and then the best move from A is back to B. It will do this until all the coal mines and oil refineries run dry.</p>

 <p>This is <em>cycling</em>, the signature failure of memoryless search, where every step the algorithm wakes up fresh with no idea it has been here before. Whether three times or over nine thousand times.</p>

 <h2>A ban list and a countdown</h2>

 <p>In tabu search, after each move, an attribute of the position you just left (in our demo, the square a queen just vacated) goes on a ban list for a handful of iterations, called its tenure.</p>

 <p>While it's on the list, the search may not move a queen back there, however tempting the move looks, and with the recent past fenced off the only direction available is somewhere new. So the ban list turns "best move" into "best move I haven't just made", a small change that punches through plateaus which hold a hill climber forever.</p>

 <p>There is one safety valve: if a banned move would produce the best solution ever seen, the ban is waived. This is the <em>aspiration criterion</em>, and it exists because tabu attributes are deliberately crude: banning a square bans every future board that would use it, including, occasionally, a record-breaking one. When the forbidden move beats anything in the run so far, take it.</p>

 <h2>Glover's tug-of-war</h2>

 <p>Glover framed the wider method as a balance between <em>intensification</em> (search hard near the good solutions you already have) and <em>diversification</em> (force yourself into regions you have never visited). Serious implementations layer on frequency memory (penalise moves you make too often), long-term restarts, and cleverer aspiration rules. It all grows from the same seed: the search takes notes on itself, and then obeys them. The demo here is a simple version.</p>

 <h2>Eight queens, no peace</h2>

 <p>The playground this time is the N-Queens problem: place N queens on an N&times;N chessboard so that none attacks another, meaning no two share a row, column, or diagonal. Our queens start one per column, so columns are handled for free, and a move relocates one queen within her own column. The score is the number of attacking pairs, and zero means solved.</p>

 <p>It's a lovely testbed for tabu search because the landscape is nothing but plateaus. Near the end you are almost always sitting at one or two conflicts, surrounded by equally mediocre neighbours. That's the terrain where greedy search paces in circles, like me trying to find my car in the multistory car park outside my work (I have the memory of a pigeon with some things).</p>

 <p>Below, a tabu search is solving the board live, and red lines join queens that currently attack each other.</p>

 <p>The frost is the memory: when a queen leaves a square that square ices over, and the frost fades as its tenure counts down, so you're watching the algorithm's short-term memory dissolve in real time. The readout underneath lists the banned squares with their countdowns. When the search overrides a ban because the move would break a record, an "aspiration!" badge lights up next to the toolbar.</p>

 

 <h2>Tenure, at zero and at twenty</h2>

 <p><strong>Switch the memory off.</strong> The search becomes plain steepest descent with sideways moves, and on a lucky board it solves anyway. Most of the time it drops to one or two conflicts and then starts pacing, and the "revisited states" counter begins to climb as it bounces between the same handful of boards, forever, at whatever speed you set. The chart flatlines with a tiny shimmer, which is the cycle itself.</p>

 <p>Flip the memory back on and the search usually punches out within a few dozen steps. One counter, and it makes the case for tabu search better than I can.</p>

 <p><strong>Set tenure to 0.</strong> A ban that expires instantly is not a ban, so this is hill climbing again, cycle and all, even with the memory checkbox ticked. Tenure is the memory. The checkbox just decides whether anyone writes to it.</p>

 <p><strong>Set tenure to 20.</strong> Now the search forbids so much of its own neighbourhood that it can barely exploit anything. It reaches a promising region, is immediately banned from refining it, and lurches away, sometimes abandoning a one-conflict board it could have finished in a step or two.</p>

 <p><strong>Grow the board.</strong> Bigger boards mean bigger neighbourhoods and longer plateaus, and the sweet-spot tenure grows with them. Around 7 suits the 8&times;8 board, and at 16&times;16 you'll want noticeably more. The literature's rule of thumb is that tenure should scale with problem size.</p>

 <h2>Remembering the good and remembering the bad</h2>

 <p>There's some interesting debates I have in my mind about this concept, because it's somewhat philosophical. Our human memories are odd, we often think of periods of time that are difficult and we dislike fondly later in life, like times in the military - we remember the fun moments and forget the 5am wake up time, endless PT and drills. But we also remember specific jarring, horrible moments with great clarity - PTSD is a big warning marker left with the intent to protect us, and let us remember what was bad.</p>

 <p>Maybe there's something to take from this: it's actually efficient to remember the bad things, but at some point to let them go, and perhaps similar can be said about remembering the good things, when trying to move towards a better future.</p>

 <div class="faq-section">
 <h2>Common questions</h2>

 <details class="faq-item">
 <summary>Why is it spelled "tabu" and not "taboo"?</summary>
 <p>Because Glover spelled it that way, and the field kept his spelling. "Tabu" is closer to the Tongan word that English borrowed "taboo" from, and both forms show up in older literature. So it's a citation rather than a typo. It also makes the algorithm much easier to search for, which is a nice side effect.</p>
 </details>

 <details class="faq-item">
 <summary>Tabu search vs simulated annealing?</summary>
 <p>They treat the same disease with opposite medicine. Annealing escapes local optima stochastically: it accepts worse moves with a probability that cools over time, so its behaviour is a distribution and every run differs. Tabu search is deterministic. Same starting board, same tenure, same run, every time (the only randomness in this demo is the scramble that deals the board). Annealing carries no memory and trusts the temperature. Tabu search carries memory and trusts the list. In practice they're complementary, and plenty of hybrid methods anneal with a tabu list bolted on.</p>
 </details>

 <details class="faq-item">
 <summary>Is tabu search used for anything real?</summary>
 <p>Heavily. It has one of the strongest production records of any metaheuristic: job-shop and staff scheduling, vehicle routing, university timetabling, telecoms network design. For years, many of the best-known solutions on standard vehicle routing benchmarks belonged to tabu search variants. Like everything in this series, it asks almost nothing of the problem, just a neighbourhood and a score, so it travels well.</p>
 </details>
 </div>

 <h2 class="section-heading" id="references">References</h2>

 <ol class="references">
          <li id="ref-1">
            Glover, F. (1986). Future paths for integer programming and links to artificial intelligence. <em>Computers &amp; Operations Research</em>, 13(5), 533&ndash;549. <a href="https://doi.org/10.1016/0305-0548(86)90048-1" target="_blank" rel="noopener">https://doi.org/10.1016/0305-0548(86)90048-1</a>
          </li>
          <li id="ref-2">
            Glover, F. (1989). Tabu Search - Part I. <em>ORSA Journal on Computing</em>, 1(3), 190&ndash;206. <a href="https://doi.org/10.1287/ijoc.1.3.190" target="_blank" rel="noopener">https://doi.org/10.1287/ijoc.1.3.190</a>
          </li>
        </ol>

        

 ]]></content:encoded>
    </item>
    <item>
      <title>Genetic Programming, Live</title>
      <link>https://www.kenreid.co.uk/blog/genetic-programming-live.html</link>
      <guid>https://www.kenreid.co.uk/blog/genetic-programming-live.html</guid>
      <pubDate>Sun, 23 Aug 2026 00:00:00 +0000</pubDate>
      <description>Genetic programming in your browser: watch a population of expression trees evolve to fit a hidden curve, read the best program as it grows, and give it bloat with the sliders.</description>
      <category>ai</category>
      <content:encoded><![CDATA[<p><em>This post includes an interactive demo that runs live in the browser. <a href="https://www.kenreid.co.uk/blog/genetic-programming-live.html">View it on the site</a> to play with it.</em></p>

 <p>Genetic programming evolves programs. What gets bred is the program itself rather than the settings of a program: a working formula nobody wrote, assembled out of worse ones. I've published with this one: the belt-laying search in <a href="https://www.kenreid.co.uk/blog/factorio-live.html">the Factorio post</a> is a genetic programming system, and it came out ahead of both other methods in the paper.</p>

 <p>This is part eight of the series. So far:</p>
 <ul>
 <li><a href="https://www.kenreid.co.uk/blog/ant-colony-live.html">ants have foraged</a></li>
 <li><a href="https://www.kenreid.co.uk/blog/evolution-live.html">triangles have evolved</a></li>
 <li><a href="https://www.kenreid.co.uk/blog/simulated-annealing-live.html">a search has cooled its way out of bad valleys</a></li>
 <li><a href="https://www.kenreid.co.uk/blog/particle-swarm-live.html">a swarm has chased its own best guess</a></li>
 <li><a href="https://www.kenreid.co.uk/blog/variable-neighbourhood-search-live.html">a stuck search has widened its neighbourhood until it came unstuck</a></li>
 <li><a href="https://www.kenreid.co.uk/blog/cma-es-live.html">a cloud of samples has learned the shape of a valley</a></li>
 <li><a href="https://www.kenreid.co.uk/blog/differential-evolution-live.html">a population has built its moves out of its own spread</a></li>
 </ul>

 

 <p>In part two every individual was a fixed-length list of numbers: fifty triangles, ten genes each, five hundred dials for evolution to twiddle. The <em>shape</em> of that genome was decided by me, in advance, forever. Genetic programming throws that away. The genome is a tree of code, and the size and shape of the tree are up for grabs too. Evolution gets the structure as well as the settings.</p>

 <div class="plain-english-box">
 <h2>Quick jargon guide</h2>
 <ul>
 <li><strong>Genetic programming (GP):</strong> an evolutionary algorithm whose individuals are programs (here, mathematical expressions) rather than fixed-length strings of parameters. Selection, crossover, and mutation operate on the program's tree.</li>
 <li><strong>Expression tree:</strong> a program written as a tree, with operators at the branches and variables or constants at the leaves. sin(x)*(x+1.7) is a seven-node tree.</li>
 <li><strong>Symbolic regression:</strong> searching for a formula that fits data, where the formula itself is the unknown, not just the coefficients of a formula you chose in advance.</li>
 <li><strong>Crossover:</strong> in GP, snipping a random subtree out of one parent and grafting in a random subtree from another. Children can be bigger than both parents or smaller than either.</li>
 <li><strong>Bloat:</strong> trees growing larger generation after generation without getting any fitter - famous disease of GP.</li>
 <li><strong>Parsimony pressure:</strong> a fitness penalty per node, taxing big trees. The standard treatment for bloat.</li>
 <li><strong>Intron:</strong> code that has no effect on a program's output, like a subtree multiplied by zero. Bloat is mostly introns.</li>
 </ul>
 </div>

 <h2>An infinite haystack</h2>

 <p>The demo below does symbolic regression. I sample forty points from a hidden target curve, and the population's job is to find an expression that fits them. Individuals are trees built from plus, minus, times, a protected divide (dividing by nearly zero returns 1 instead of exploding), sine, cosine, the variable x, and random constants.</p>

 <p>Fitness is mean squared error over the sample points, the lower the better. A formula that outputs infinity or NaN anywhere gets a score so bad it amounts to a death sentence.</p>

 <p>Part two's genome was a box with five hundred dials: finite and fixed. The space of all expressions is infinite, and almost all of it is garbage. A random tree comes out like cos(x/x)*0.3-x. There's no gradient to follow and no obvious notion of "nearby".</p>

 <p>And yet a population of these things, bred and culled, finds working programs in seconds - which is kinda crazy.</p>

 <h2>Trading subtrees</h2>

 <p>The engine is the one you already know from part two: tournament selection, crossover, mutation, and a couple of elites carried into each new generation untouched. What changes is what crossover <em>means</em>. With trees, crossover picks a random subtree in each parent and swaps them. It works because subtrees are meaningful units in a way that random substrings of characters never are: sin(2*x) is a working component. Evolution here is trading parts, not shuffling letters.</p>

 <p>The starting population is grown "ramped half-and-half", a mix of bushy full trees and scraggly random ones at several depths (GP-speak for seeding the pool with variety). I also cap tree depth at eight, partly for your device's sake and partly because, as you'll see, GP will take every inch you give it.</p>

 <h2>Watch a program grow</h2>

 <p>Below, a population of formulas is evolving against the data. The dots are the samples from the hidden curve, and the green line is the best formula so far, redrawn each generation so you can watch it snap toward the data.</p>

 <p>The node diagram is that formula's actual code: blue nodes are operators, amber leaves are x and constants. The readout underneath prints it as maths you can read. Generations tick by a few per second so the motion stays watchable.</p>

 

 <h2>The parsimony tax</h2>

 <p><strong>Set parsimony to zero.</strong> Keep one eye on the average size chart. Error improves, then plateaus, while average size climbs and keeps climbing until it slams into the depth cap.</p>

 <p>The population is now hauling around programs of a hundred-plus nodes that fit the data no better than a fifteen-node formula from fifty generations ago. This is bloat, the most famous disease of genetic programming and the reason the parsimony slider exists. Why a population would <em>choose</em> to bloat gets its own section below.</p>

 <p><strong>Now slam parsimony to maximum.</strong> The opposite catastrophe: every node now costs more fitness than accuracy is worth, so the population collapses to tiny, dumb expressions: a bare constant, a lonely x. They fit the data terribly, but cheaply. Parsimony is a tax, and like any tax, set it too high and nobody builds anything.</p>

 <p><strong>Shrink the population to twenty.</strong> The run crawls or stalls. With so few trees, the pool of subtrees for crossover to trade is tiny, and once the population homogenises, mutation has to invent every new part from scratch.</p>

 <p><strong>And drop mutation to zero</strong> while you're at it. Same lesson as part two, sharper here: crossover can only recombine subtrees that already exist somewhere in the population. Once the interesting ones are gone, they are gone.</p>

 <h2>Bloat is evolved defensive code</h2>

 <p>Subtree crossover is destructive: graft a random subtree into a random point of a working program and you'll usually break it.</p>

 <p>Imagine two parents of equal fitness, one lean, one padded with introns: code that computes something and then multiplies it by zero, or adds a quantity and subtracts it again. When crossover hits the padded parent, odds are it lands in the padding, and the child behaves just like its parent. When it hits the lean one, it hits something that mattered.</p>

 <p>Padded parents therefore produce more viable children. Padding spreads. Average size marches upward while fitness stands perfectly still.</p>

 <p>The population is evolving <em>resistance to its own genetic operators</em>. No line of my code rewards defensive padding: it falls out of selection pressure, the same way real genomes are stuffed with non-coding DNA and real codebases accrete dead branches nobody dares delete (you don't want to see some of the folders I don't sync to GitHub here locally...).</p>

 <p>The trees are, in effect, growing bubble wrap to survive crossover.</p>

 <h2>Where GP turns up in practice</h2>

 <p>Symbolic regression is the flagship application and it earns its keep in science. Fit a neural network to experimental data and you get a black box that interpolates. Fit an expression tree and you get an equation a human can read and argue with.</p>

 <p>GP-based systems have rediscovered conservation laws from raw motion-capture data, and they get used to propose compact models in physics, materials science and ecology. When you want insight rather than raw predictive power, a ten-node formula beats a million-weight network, contrary to what many machine learning specialists would say.</p>

 <p>NASA's ST5 mission needed a small satellite antenna with an awkward radiation pattern, and an evolutionary algorithm designed one<sup><a href="https://www.kenreid.co.uk/blog/genetic-programming-live.html#ref-3" class="cite-ref">[3]</a></sup>. The result looks like a bent paperclip. No engineer would have drawn it, and it outperformed the conventionally designed alternative.</p>

 <p>It flew in 2006. There is evolved hardware in space!</p>

 <p>John Koza launched the field with his 1992 book<sup><a href="https://www.kenreid.co.uk/blog/genetic-programming-live.html#ref-1" class="cite-ref">[1]</a></sup> and then spent years compiling "human-competitive" results: GP runs that reinvented patented circuits and produced patentable designs of their own. </p>

 <p>Every one of them lived in a domain with a fast, automatic, well-behaved fitness function. GP never became a general way to write software, and the field guide I recommend below<sup><a href="https://www.kenreid.co.uk/blog/genetic-programming-live.html#ref-2" class="cite-ref">[2]</a></sup> is refreshingly frank about why. Where a score is cheap to compute and means what you want, GP is a live option. Everywhere else, the fitness function is the really hard part.</p>

 <h2>Structure is a freedom</h2>

 <p>Part two's lesson was that the fitness function is where the meaning lives: change what you reward and you change what evolves. Give evolution control over structure as well and it will use that control in every way that pays.</p>

 <p>It builds the subtree that fits your curve. It also builds padding to shield that subtree from your own crossover operator, and for the same reason: both help its children survive.</p>

 <p>Zero the parsimony and watch the trees fatten in real time, then tax them back down to stumps. Sit through thirty flat generations of the error chart until it falls off a cliff, because two mediocre parents happened to be holding complementary halves of the answer.</p>

 <p>An infinite search space, almost all of it garbage, explored by trading parts and taxing waste. It should not work anything like as well as it does.</p>

 <div class="faq-section">
 <h2>Common questions</h2>

 <details class="faq-item">
 <summary>Is this how AI coding assistants work?</summary>
 <p>No. A modern code assistant is a large language model: it learned from an enormous corpus of human-written code and generates likely text, so it turns up to every problem with a vast prior about what programs should look like. GP turns up knowing nothing. It has never seen a maths textbook. It has a score, and it searches blind. That makes it wasteful (it rediscovers wheels constantly) and also unprejudiced: it can find designs no human would write, like that antenna. Researchers have begun combining the two, with a language model proposing programs and an evolutionary loop scoring and selecting them.</p>
 </details>


 </div>

 <h2 class="section-heading" id="references">References</h2>

 <ol class="references">
          <li id="ref-1">
            Koza, J. R. (1992). <em>Genetic Programming: On the Programming of Computers by Means of Natural Selection</em>. MIT Press. ISBN 978-0262111706.
          </li>
          <li id="ref-2">
            Poli, R., Langdon, W. B., &amp; McPhee, N. F. (2008). <em>A Field Guide to Genetic Programming</em>. Freely available at <a href="http://www.gp-field-guide.org.uk" target="_blank" rel="noopener">http://www.gp-field-guide.org.uk</a>
          </li>
          <li id="ref-3">
            Lohn, J. D., Hornby, G. S., &amp; Linden, D. S. (2005). An evolved antenna for deployment on NASA's Space Technology 5 mission. In <em>Genetic Programming Theory and Practice II</em> (pp. 301&ndash;315). Springer.
          </li>
        </ol>

        

 ]]></content:encoded>
    </item>
    <item>
      <title>Differential Evolution, Live</title>
      <link>https://www.kenreid.co.uk/blog/differential-evolution-live.html</link>
      <guid>https://www.kenreid.co.uk/blog/differential-evolution-live.html</guid>
      <pubDate>Sat, 22 Aug 2026 00:00:00 +0000</pubDate>
      <description>Differential evolution running live in your browser: a population whose own spread sets its step size hunts minima on four landscapes.</description>
      <category>ai</category>
      <content:encoded><![CDATA[<p><em>This post includes an interactive demo that runs live in the browser. <a href="https://www.kenreid.co.uk/blog/differential-evolution-live.html">View it on the site</a> to play with it.</em></p>

 <p>This is part seven of the Algorithms, Live series. So far:</p>
 <ul>
 <li><a href="https://www.kenreid.co.uk/blog/ant-colony-live.html">ants have foraged</a></li>
 <li><a href="https://www.kenreid.co.uk/blog/evolution-live.html">triangles have evolved</a></li>
 <li><a href="https://www.kenreid.co.uk/blog/simulated-annealing-live.html">a search has cooled its way out of bad valleys</a></li>
 <li><a href="https://www.kenreid.co.uk/blog/particle-swarm-live.html">a swarm has chased its own best guess</a></li>
 <li><a href="https://www.kenreid.co.uk/blog/variable-neighbourhood-search-live.html">a stuck search has widened its neighbourhood until it came unstuck</a></li>
 <li><a href="https://www.kenreid.co.uk/blog/cma-es-live.html">a cloud of samples has learned the shape of a valley</a></li>
 </ul>

 <p>Today's algorithm is for hard continuous problems: differential evolution. It's one of the simpler of the evolutionary family, a few lines of arithmetic wrapped in a loop. I was first properly introduced to it by Prof. Cedric Gondro, at Michigan State University, as it's one of his favorite algorithms, so we were considering its use for genomic prediction problems.</p>

 

 <div class="plain-english-box">
 <h2>Quick jargon guide</h2>
 <ul>
 <li><strong>Differential evolution (DE):</strong> an optimiser that improves a population of candidate points by adding scaled differences between population members to other members, keeping each change only if it scores better.</li>
 <li><strong>Difference vector:</strong> the arrow from one population member to another (\(b - c\)). It encodes a direction and a distance the population currently spans, and it is the raw material every DE move is built from.</li>
 <li><strong>Mutant / donor vector:</strong> the new candidate built by adding a scaled difference vector to a third member: \(a + F(b - c).\) Also called the donor, because it donates coordinates during crossover.</li>
 <li><strong>Crossover rate (CR):</strong> the probability that each coordinate of the trial point is taken from the mutant rather than from the parent. High \(\mathit{CR}\) means big coordinated moves. Low \(\mathit{CR}\) means one-coordinate-at-a-time nudges.</li>
 <li><strong>Scale factor (F):</strong> the multiplier on the difference vector. It scales the population's own geometry up or down but never replaces it.</li>
 <li><strong>Premature convergence:</strong> the population huddling into one small region before it has found the best basin. In DE this is doubly fatal, because a huddled population can only generate tiny difference vectors, so it loses the ability to leave.</li>
 </ul>
 </div>

 <h2>Subtraction as a search strategy</h2>

 <p>Here is DE/rand/1/bin<sup><a href="https://www.kenreid.co.uk/blog/differential-evolution-live.html#ref-1" class="cite-ref">[1]</a></sup>: keep a population of points scattered over the landscape. To improve any one of them:</p>

 <ol>
 <li>Pick three other members at random: \(a\), \(b\), and \(c\).</li>
 <li>Build a mutant: take the difference vector from \(c\) to \(b\), scale it by \(F\), and add it to \(a\). In symbols, \(a + F(b - c).\)</li>
 <li>Crossover: build a trial point by taking each coordinate from the mutant with probability \(\mathit{CR}\), otherwise keeping the parent's coordinate. (One coordinate is always taken from the mutant, so the trial is never a clone of the parent.)</li>
 <li>Greedy selection: score the trial. If it beats the parent, it takes the parent's seat in the population. If not, it is thrown away and the parent stays.</li>
 </ol>

 <p>Do that once for every member to create a generation. Repeat!</p>

 <p>Every other optimiser in this series had to decide how big a move to make: simulated annealing had a cooling schedule, the genetic algorithm had a mutation rate, gradient descent has a learning rate. DE samples a difference between two of its own members and uses <em>that</em> as the move.</p>

 <p>Early in a run the population is sprayed across the whole landscape, so the differences are long and the moves are large. As the population drains into a valley, its members bunch together, the differences between them shrink, and the move size shrinks too. </p>

 <p>The difference vectors shrink, and they also point somewhere useful. If the population has settled along a curved valley, the differences between members mostly run along that valley, so mutants get launched along it too.</p>

 <p>The population becomes a rough map of the terrain it's sitting on. The state you already have to keep doubles as the search geometry.</p>

 <h2>Watch the geometry</h2>

 <p>You probably get this now if you're reading through the series, but here goes: the demo below runs DE on a choice of four classic test landscapes. The coloured ground is the fitness surface (deeper blue is worse, the ground fades toward the background where it is low, and a small cross marks the global minimum). The blue dots are the population, and the green ring is the best point found so far.</p>

 <p>Each generation, one member of the population gets its donor construction drawn in full: the three amber dots are \(a\), \(b\), and \(c\), the dashed amber arrow is the difference vector from \(c\) to \(b\), and the red arrow is that same vector scaled by \(F\) and grafted onto \(a\), ending at the mutant (the hollow red circle). The red diamond is the trial point after crossover, ringed if it won its duel with the parent.</p>

 <p>Then compare the arrows at generation two with the arrows at generation sixty. Same code, same \(F\), and the moves have shrunk by orders of magnitude because the population did.</p>

 

 <h2>F, CR, and a population of eight</h2>

 <p><strong>Drop \(\boldsymbol{F}\) to 0.1 on the Egg carton.</strong> Every move is now a tenth of a population difference, so members can only creep to the nearest dimple. The spread collapses early, usually before anyone has found the central basin.</p>

 <p>Then the trap springs. A huddled population can only make tiny difference vectors, so the step size follows the spread down and the search is welded shut. Self-scaling, run in reverse. The best-fitness line goes flat while the spread line dives, and that is premature convergence.</p>

 <p><strong>Now push \(\boldsymbol{F}\) to 1.8.</strong> Every mutant overshoots the region the population spans, so the population becomes a spray. The spread line stops falling, the mean step stays huge, and best fitness improves only by luck. DE needs the population to contract for its thermometer to read cooler. At \(F\) this high, contraction never begins.</p>

 <p><strong>Set \(\boldsymbol{\mathit{CR}}\) near zero on the Banana valley.</strong> Each trial now differs from its parent in a single coordinate, so the search moves like a rook: horizontally or vertically, never diagonally. The banana valley is curved, and progress along it needs both coordinates to change together.</p>

 <p>Watch the population inch along in tiny axis-aligned stair steps. Then push \(\mathit{CR}\) back up to 0.9 and watch it slide along the valley in coordinated diagonal moves. \(\mathit{CR}\) is the knob that decides whether DE moves one coordinate at a time or as whole vectors.</p>

 <p><strong>Shrink the population to 8.</strong> The difference vectors are now drawn from a pool of a few dozen possible pairs, so the same handful of directions gets recycled over and over.</p>

 <p>Diversity is essential and a tiny population runs out of it almost immediately: fast convergence, frequently to the wrong dimple. The genetic algorithm in part two had the same failure for the same reason, a gene pool too small to explore with.</p>

 <h2>The missing knob</h2>

 <p>Put this next to the genetic algorithm from part two and the family resemblance is obvious: a population, mutation, crossover, selection. The GA shuffled bits and letters. DE does arithmetic on real vectors. The GA made mutation from coin flips and a fixed rate you had to tune. DE makes mutation out of the population itself, so the operator adapts as the population does.</p>

 <p>And the GA ran a selection lottery where fit parents got more tickets. DE holds one duel per seat, child against its own parent, winner sits. The best member can only be replaced by something better.</p>

 <p>Its sophisticated cousin <a href="https://www.kenreid.co.uk/blog/cma-es-live.html">CMA-ES</a> learns an explicit statistical model of where good moves live and updates it with some fairly serious mathematics. DE gets a decent share of the same benefit, steps that stretch along the good directions and shrink as the search closes in, using subtraction.</p>

 <p>It is dumber than CMA-ES generally, but very much stands on its own, regardless!</p>

 <h2>Where DE turns up in practice</h2>

 <p>Storn and Price introduced it in 1997<sup><a href="https://www.kenreid.co.uk/blog/differential-evolution-live.html#ref-1" class="cite-ref">[1]</a></sup>, it won early optimisation competitions, and it is still a default first try in engineering optimisation: antenna design, power systems, chemical process tuning, anywhere someone has a black-box simulator with a dozen real-valued dials. The survey literature on its variants runs to hundreds of papers<sup><a href="https://www.kenreid.co.uk/blog/differential-evolution-live.html#ref-2" class="cite-ref">[2]</a></sup>.</p>

 <p>You can buy adaptivity: learned models, schedules, meta-parameters. Or you can arrange the algorithm so the state it already keeps <em>is</em> the adaptation. DE gets self-scaling search out of a subtraction.</p>

 <p>Go and set the Egg carton running, click somewhere daft to strand the population in a corner, and watch the arrows do the rest.</p>

 <div class="faq-section">
 <h2>Common questions</h2>

 <details class="faq-item">
 <summary>Why is it called "differential" evolution?</summary>
 <p>For the difference vectors: every mutation is built from the difference between two population members. It has nothing to do with differential equations or with derivatives.</p>
 </details>

 <details class="faq-item">
 <summary>How do I pick F and CR in practice?</summary>
 <p>The folklore defaults hold up surprisingly well: \(F\) between 0.5 and 0.9, \(\mathit{CR}\) around 0.9. This demo starts at \(F = 0.8\), \(\mathit{CR} = 0.9\). Low \(\mathit{CR}\) only helps when your variables are independent of each other, which you rarely know in advance, so high \(\mathit{CR}\) is the safer bet. If you'd rather not choose at all, self-adaptive variants such as jDE and SaDE evolve \(F\) and \(\mathit{CR}\) alongside the solutions, and the Das and Suganthan survey covers them at length<sup><a href="https://www.kenreid.co.uk/blog/differential-evolution-live.html#ref-2" class="cite-ref">[2]</a></sup>.</p>
 </details>

 <details class="faq-item">
 <summary>Doesn't greedy selection cause the premature convergence you just demonstrated?</summary>
 <p>Less than you'd think. The duel is greedy about <em>who survives</em>, but the moves themselves come from the whole population's geometry, and that keeps exploration alive as long as the population is spread out. What kills the search in the \(F = 0.1\) experiment is the spread collapsing before the good basin is found. Greedy replacement plus population-shaped mutation is a surprisingly stable pairing, and it's a big part of why DE needs so little babysitting.</p>
 </details>
 </div>

 <h2 class="section-heading" id="references">References</h2>

 <ol class="references">
          <li id="ref-1">
            Storn, R., &amp; Price, K. (1997). Differential Evolution - A Simple and Efficient Heuristic for global Optimization over Continuous Spaces. <em>Journal of Global Optimization</em>, 11(4), 341&ndash;359. <a href="https://doi.org/10.1023/A:1008202821328" target="_blank" rel="noopener">https://doi.org/10.1023/A:1008202821328</a>
          </li>
          <li id="ref-2">
            Das, S., &amp; Suganthan, P. N. (2011). Differential Evolution: A Survey of the State-of-the-Art. <em>IEEE Transactions on Evolutionary Computation</em>, 15(1), 4&ndash;31. <a href="https://doi.org/10.1109/TEVC.2010.2059031" target="_blank" rel="noopener">https://doi.org/10.1109/TEVC.2010.2059031</a>
          </li>
        </ol>

        

 ]]></content:encoded>
    </item>
    <item>
      <title>CMA-ES, Live</title>
      <link>https://www.kenreid.co.uk/blog/cma-es-live.html</link>
      <guid>https://www.kenreid.co.uk/blog/cma-es-live.html</guid>
      <pubDate>Wed, 19 Aug 2026 00:00:00 +0000</pubDate>
      <description>CMA-ES live in your browser: watch an optimiser learn the shape of a valley by stretching and rotating its search cloud until the narrow floor costs it nothing to walk along.</description>
      <category>ai</category>
      <content:encoded><![CDATA[<p><em>This post includes an interactive demo that runs live in the browser. <a href="https://www.kenreid.co.uk/blog/cma-es-live.html">View it on the site</a> to play with it.</em></p>

 <p>There is a shape of problem that many optimisers struggle with: a long, thin, tilted valley. </p>

 <p>Every algorithm in this series so far has moved in steps whose size and shape were settled in advance:</p>
 <ul>
 <li><a href="https://www.kenreid.co.uk/blog/ant-colony-live.html">the ants of part 1</a> took steps along edges and left scent behind</li>
 <li><a href="https://www.kenreid.co.uk/blog/evolution-live.html">the genetic algorithm of part 2</a> took steps in genome space, one mutation at a time</li>
 <li><a href="https://www.kenreid.co.uk/blog/simulated-annealing-live.html">simulated annealing in part 3</a> had a step size you could watch shrink as the temperature fell</li>
 </ul>

 <p>This one learns the <em>shape</em> of the neighbourhood worth searching, and restretches itself to match: CMA-ES.</p>

 

 <div class="plain-english-box">
 <h2>Quick jargon guide</h2>
 <ul>
 <li><strong>CMA-ES:</strong> the Covariance Matrix Adaptation Evolution Strategy. An optimiser that samples a cloud of candidate points, keeps the best ones, and keeps adjusting the cloud's position, size, and shape to follow whatever has been working.</li>
 <li><strong>Evolution strategy:</strong> the family CMA-ES belongs to: evolutionary optimisers for continuous variables, where mutation means adding random numbers rather than flipping genes.</li>
 <li><strong>Mean:</strong> the centre of the search cloud, the algorithm's current best guess at where the answer lives.</li>
 <li><strong>Step size (sigma):</strong> the overall scale of the cloud. Big sigma means bold, scattered samples; small sigma means cautious, local ones.</li>
 <li><strong>Covariance matrix:</strong> the shape of the cloud: which directions it stretches along and how far. This is the star of the show, and adapting it is what the "CMA" stands for.</li>
 <li><strong>Condition number:</strong> how stretched the cloud is: the ratio of its longest axis to its shortest. 1 is a circle; 400 is a needle.</li>
 <li><strong>Black-box optimisation:</strong> optimising a function you can only evaluate, not differentiate or inspect. You put numbers in, a score comes out.</li>
 </ul>
 </div>

 <h2>Steps versus shapes</h2>

 <p>Valleys like that are everywhere in real problems (the technical word is ill-conditioned). An optimiser proposing moves in random directions wastes almost all of them climbing walls while gradient descent zigzags across the floor of possible solutions.</p>

 <p>This requires an optimiser that notices "movement along <em>this</em> axis keeps paying off, movement along <em>that</em> one never does".</p>

 <p>CMA-ES keeps a sampling distribution, a fuzzy ellipse of probability. Each generation it draws a cloud of candidate points from the ellipse, scores them, and shifts the centre toward the better half.</p>

 <p>It also updates the ellipse's <em>shape</em>. Directions the winners recently moved in get widened. Directions that never produced a winner get squeezed. It saves the historic data of wins in the covariance matrix.</p>

 <p>Instead of a fixed cooling schedule, CMA-ES watches its own recent movement. If successive generations keep marching the same way, the steps were too small, so sigma grows. If the path folds back on itself, the steps were too big, so sigma shrinks. Exploration vs exploitation.</p>

 <p>Hansen and Ostermeier formalised all of this in 2001<sup><a href="https://www.kenreid.co.uk/blog/cma-es-live.html#ref-1" class="cite-ref">[1]</a></sup>, and the design is careful enough that the standard version has no parameters you are expected to tune. But, in this version, for fun, there are sliders so you can override that.</p>

 <h2>The loop on a napkin</h2>

 <ol>
 <li>Sample a cloud of candidate points from a Gaussian: centre (mean), scale (sigma), shape (covariance).</li>
 <li>Score every point and rank them.</li>
 <li>Move the mean toward a weighted average of the best few, best weighted most.</li>
 <li>Stretch the covariance along the directions those winners came from.</li>
 <li>Grow or shrink sigma depending on whether recent moves kept pointing the same way.</li>
 <li>Go to step 1.</li>
 </ol>

 <h2>Watch it</h2>

 <p>Below, CMA-ES is minimising a landscape drawn as contour shading (darker is worse, the red cross is the global optimum). Blue dots are one generation's samples, the amber rings are the search ellipse at one and two sigma, and the green dot with the fading tail is the mean and the route it took.</p>

 <p>Start with the default narrow valley and watch the ellipse for the first few seconds. It begins as a circle, tips over to match the valley's angle, and stretches. Click or drag anywhere on the map to plant the start point somewhere new.</p>

 

 <h2>The step-size rule</h2>

 <p>On the smooth valleys the run still works, it just crawls. The step-size rule notices the steady progress and inflates sigma back to something sensible, which you can watch happening on the right-hand chart.</p>

 <p>On the egg carton a tiny sigma is fatal. The cloud fits inside a single dimple, every sample agrees the local floor is the best thing going, and the run settles into the nearest dent.</p>

 <p>If the sigma is huge, the first generations are chaos.</p>

 <p>Then watch the sigma chart. The path the mean traces keeps folding back on itself, the adaptation rule reads that as "steps too big", and sigma falls generation after generation until the cloud shrinks onto something worth exploiting. Thrashing and then recovery is the self recovering step size.</p>

 <p>Drop lambda to 6 and each generation's ranking is built from almost nothing. The mean update gets noisy and the covariance estimate gets noisier.</p>

 <p>Back to the narrow valley, default settings, and this time watch only the amber ellipse and the stretch readout. Circle, tilt, stretch, and then a needle sliding along the valley floor with the stretch number climbing into the hundreds.</p>

 <p>Every other algorithm I've covered in this series would be paying the valley's toll on every single move, but CMA-ES redrew its own geometry so there is no toll left to pay!</p>

 <h2>A memory of directions</h2>

 <p>What I like most about CMA-ES is where it keeps what it has learned. The ants kept their knowledge in the world, as scent on edges. The genetic algorithm kept it in a population, spread implicitly across genomes: variety, but no geometry. Simulated annealing had one knob, a step size, and its step size only goes down.</p>

 <p>CMA-ES keeps a small, dense summary: one point for "where", one number for "how far", and one matrix for "which way". The matrix is the difference. It is a running record of the directions that recently succeeded, updated every generation, and it turns a search that happens <em>in</em> the landscape into one that builds a model <em>of</em> the landscape's local shape.</p>

 <h2>Where it gets used</h2>

 <p>For continuous black-box problems, where the objective is a simulator or a physical measurement or a hyperparameter score with no gradient in sight, CMA-ES is the default serious tool up to a few hundred dimensions (yes, you read that correctly!!). </p>

 <p>It has tuned aircraft geometry, robot gaits, controller parameters, and the settings of other learning systems. Hansen's tutorial<sup><a href="https://www.kenreid.co.uk/blog/cma-es-live.html#ref-2" class="cite-ref">[2]</a></sup>.</p>

 <p>When progress stalls, the instinct is to push harder in the direction you were already going. CMA-ES keeps an estimate of which directions have been rewarding you lately, and spends your effort along those, even when they aren't the ones you set off in. Which is surprisingly good life advice to get from a pile of linear algebra.</p>

 <div class="faq-section">
 <h2>Common questions</h2>

 <details class="faq-item">
 <summary>Is this the full CMA-ES?</summary>
 <p>Simplified. The demo does the real core: Gaussian sampling, ranking, log-weighted recombination of the best mu samples, the rank-mu covariance update, and cumulative (path-based) step-size adaptation. It leaves out the rank-one covariance update with its separate evolution path, plus restarts and boundary handling, which the standard implementations all include. </p>
 </details>

 <details class="faq-item">
 <summary>Why does it keep drifting after finding the optimum?</summary>
 <p>Because it never knows it has found it. CMA-ES only ever sees relative rankings inside its own cloud, never "you are done". </p>
 </details>

 <details class="faq-item">
 <summary>When would I not use CMA-ES?</summary>
 <p>When you have gradients, use them. On smooth, differentiable problems, and especially in the millions of dimensions of neural network training, gradient methods win by orders of magnitude. </p>
 </details>
 </div>

 <h2 class="section-heading" id="references">References</h2>

 <ol class="references">
          <li id="ref-1">
            Hansen, N., &amp; Ostermeier, A. (2001). Completely derandomized self-adaptation in evolution strategies. <em>Evolutionary Computation</em>, 9(2), 159&ndash;195. <a href="https://doi.org/10.1162/106365601750190398" target="_blank" rel="noopener">https://doi.org/10.1162/106365601750190398</a>
          </li>
          <li id="ref-2">
            Hansen, N. (2016). The CMA Evolution Strategy: A Tutorial. <em>arXiv preprint</em> arXiv:1604.00772. <a href="https://doi.org/10.48550/arXiv.1604.00772" target="_blank" rel="noopener">https://doi.org/10.48550/arXiv.1604.00772</a>
          </li>
        </ol>

        

 ]]></content:encoded>
    </item>
    <item>
      <title>Factorio, Live: a Game as an Optimisation Benchmark</title>
      <link>https://www.kenreid.co.uk/blog/factorio-live.html</link>
      <guid>https://www.kenreid.co.uk/blog/factorio-live.html</guid>
      <pubDate>Sun, 16 Aug 2026 00:00:00 +0000</pubDate>
      <description>The logistic transport belt problem from my GECCO 2021 paper, running live in your browser: watch simulated annealing and genetic programming lay belts across a walled grid until the items get through.</description>
      <category>data science</category>
      <content:encoded><![CDATA[<p><em>This post includes an interactive demo that runs live in the browser. <a href="https://www.kenreid.co.uk/blog/factorio-live.html">View it on the site</a> to play with it.</em></p>

 <p>Factorio is a game I've loved since it was released. It's my most-played game on Steam, and there's something so deeply satisfying about playing a game all about optimization, exploring and solving problems. When I started thinking about the psychology behind this, I realized that video games, puzzles, board games and the like mirror a satisfaction I find when programming, conducting research and solving real-world problems. It scratches an itch in a particular way unfound in other walks of life: defeating challenges beyond a grind in an RPG, lifting a heavier weight at the gym or accomplishing projects based more on time than difficulty. It's the slow accrual of problems solved into a portfolio of solutions working in tandem, so you can zoom out to the map and say "look at my base! it's a bit of a mess, but it's mine, and I love it."</p>

 <p>I decided to bridge the gap between the video game and the research, both of which scratched that itch in my mind. I brought up the idea with one of my supervisors at Michigan State University (MSU) at the time, <a href="https://scholar.google.com/citations?user=u-FjxEUAAAAJ&amp;hl=en" target="_blank" rel="noopener noreferrer">Prof. Wolfgang Banzhaf</a>. He thought it sounded interesting, and liked my point that a video game is a better teaching instrument than a textbook, since this visually shows success and failure, and the mechanics of individual algorithms live in front of you. He said I should go for it, so I enlisted a couple of my colleagues (now co-authors) and we worked on it together. 

 

 <div class="plain-english-box">
 <h2>Quick jargon guide</h2>
 <ul>
 <li><strong>Operational research:</strong> the study of running things well: what to build, where to put it, and in what order. </li>
 <li><strong>Throughput and bottleneck:</strong> how much gets through per second and what component causes a trickle for the rest of a system.</li>
 <li><strong>Transport belt:</strong> the conveyor that moves items in Factorio. Its capacity is fixed, so a longer route costs time, not bandwidth.</li>
 <li><strong>Underground belt:</strong> a pair of entry and exit tiles that carries items beneath obstacles, up to four cells apart. It's kind of like teleporting that takes the same amount of time as a normal belt.</li>
 <li><strong>Metaheuristic:</strong> a search that improves a solution by trial and feedback rather than solving equations. <a href="https://www.kenreid.co.uk/blog/simulated-annealing-live.html">Simulated annealing</a> and <a href="https://www.kenreid.co.uk/blog/evolution-live.html">genetic algorithms</a> are both in this family.</li>
 </ul>
 </div>

 <h2>Watch it lay the belts</h2>

 


  <h2>Two places this demo is not the paper</h2>

 <p>The scoring here is not identical to the published version. The paper scored a candidate by running it in the game, which returns nothing at all until the route lands. That cliff is survivable for a method that lays whole paths in one move, and hopeless for one that edits a single cell at a time: annealing would sit at zero for thousands of evaluations and look broken. So the demo adds partial credit for reaching further east, and a small bonus for using fewer belts once connected. It is a shaped objective, and shaped objectives can be gamed, so this is really just for demo purposes, not a realistic solution (mostly since "east" isn't always the goal).</p>

 <p>The second departure is in how annealing picks what to change. The paper varies its neighbour selection with iteration progress. Here, two thirds of edits are at the frontier, meaning the cell where the current route gives up. The reason is arithmetic: on a twelve by twelve grid, a uniformly chosen cell is almost always somewhere the route never reaches, so most edits are invisible to the score and the search spends its budget rearranging scenery. Both changes exist to make the comparison fair to annealing, which is the direction a bias should run if you are going to have one.</p>

 <p>So: this is a pretty and intentionally non-representative demo of the paper.</p>

 <h2>What the paper actually did</h2>

  <p>The paper behind this is <a href="https://scholar.google.com/citations?view_op=view_citation&amp;hl=en&amp;user=bwl2u00AAAAJ&amp;citation_for_view=bwl2u00AAAAJ:8k81kl-MbHgC" target="_blank" rel="noopener noreferrer"><em>The Factory Must Grow: Automation in Factorio</em></a>, written with my co-authors Iliya Miralavy, Stephen Kelly, Wolfgang Banzhaf and Cedric Gondro, and presented at GECCO in 2021. The starting point was that Factorio is a factory simulator that has already done the expensive part of building a benchmark: it has consistent physics, a fixed belt throughput, and a community of players who have spent years discovering what good layouts look like, so how would our algorithms fare?</p>

 <p>We defined the logistic transport belt problem formally, as an integer program with hard constraints on what can occupy a cell and how underground pairs must line up, and two soft constraints to be maximised: items delivered to the receiver, and items accepted from the chest. Then we built the 'Factorio Optimizer Interface', which uses the game's Lua modding hooks and the RCON protocol to place a candidate layout in a running Factorio server and read back how many items the input inserter took and how many reached the output chest. Because it talks over a socket and reads matrices from disk, the optimiser can be written in anything: ours were in Java, Python and C++. The evaluation function is the game itself.</p>

 <p>Three optimisers were compared on six instances, being three grid sizes at three by three, six by six and twelve by twelve, each with and without obstacles. Parallel simulated annealing, a genetic programming system whose operators place and connect belt runs rather than editing cells, and an evolutionary reinforcement learning approach. The small instances are solvable by anything. The twelve by twelve with obstacles is where things really got interesting.</p>

 <p>Genetic programming did well here because its operators fit the problem: laying a run of belts and connecting two points are single moves, so one step of the search covers ground that costs annealing a long chain of individually pointless edits. </p>

 <figure>
 <img src="https://www.kenreid.co.uk/blog/img/factorio-paper-12x12-ingame.webp" alt="A Factorio screenshot of the twelve by twelve benchmark instance: a square arena walled in two-deep stone, with a steel chest and inserter at the top right and a receiver at the bottom left. Yellow transport belts wind between tree stumps that act as obstacles, and a line of iron ore rides the belts along the route between the two." loading="lazy" class="kr-figure-img" style="max-width: 620px;" width="1100" height="1151">
 <figcaption class="figure-note">From <a href="https://scholar.google.com/citations?view_op=view_citation&amp;hl=en&amp;user=bwl2u00AAAAJ&amp;citation_for_view=bwl2u00AAAAJ:8k81kl-MbHgC" target="_blank" rel="noopener noreferrer">Reid et al. (2021)</a>. The twelve by twelve instance with obstacles, as the optimiser sees it: walls two cells thick, input top right, receiver bottom left, and the stumps are the cells you cannot build on. The ore on the belts is the fitness function happening.</figcaption>
 </figure>

 <h2>The model</h2>

 <p>Section 4 of the paper states the problem formally. Two hard constraints, two soft ones, and a pair of binary decision variables. Throughput, routing and flow is absent, for a reason I will come back to.</p>

 <p>A position \(p\) is a coordinate pair in the grid. Directions are \(d \in \{0,1,2,3\}\), meaning north, east, south and west. Belts and underground belts are drawn from \(L_b\) and \(L_u\), and \(\phi_p\) is 1 when position \(p\) holds an obstacle and 0 otherwise. The decisions are whether to place a given belt type, at a given position, facing a given way:</p>

 <div class="math-block">
 \[
 \begin{aligned}
 X_{bcpd} &= 0 \text{ or } 1, && \forall b \in L_b,\; \forall p_x \in G_x,\; \forall p_y \in G_y,\; \forall d \in \{0,1,2,3\} \\[4pt]
 X_{ucpd} &= 0 \text{ or } 1, && \forall u \in L_u,\; \forall p_x \in G_x,\; \forall p_y \in G_y,\; \forall d \in \{0,1,2,3\}
 \end{aligned}
 \]
 </div>

 <figure>
 <img src="https://www.kenreid.co.uk/blog/img/factorio-paper-encodings.webp" alt="A table of integer encodings. Negative numbers describe the problem: minus ten to minus seven are output directions, minus six a steel chest, minus five to minus two input directions, minus one a stone wall, and zero is empty. Positive numbers are the solution: one to four are belts facing north, east, south and west, and five to eight are underground belts in the same four directions." loading="lazy" class="kr-figure-img" style="max-width: 420px;" width="590" height="546">
 <figcaption class="figure-note">From <a href="https://scholar.google.com/citations?view_op=view_citation&amp;hl=en&amp;user=bwl2u00AAAAJ&amp;citation_for_view=bwl2u00AAAAJ:8k81kl-MbHgC" target="_blank" rel="noopener noreferrer">Reid et al. (2021)</a>. The encoding an optimiser reads and writes. Everything below zero is the problem and is fixed; everything above zero is the solution and is yours. A direction is not a separate field, it is baked into the number.</figcaption>
 </figure>

 <p>The objective maximises the sum of the two soft constraints, each a parameterised item count scaled by a weight (not as in how heavy it is, but a weighting as in scaling), where \(o\) is the number of items output and \(i\) the number input:</p>

 <div class="math-block">
 \[
 \begin{aligned}
 \max \quad & (SC_1 + SC_2), \qquad SC_1 = o \, w_1, \quad SC_2 = i \, w_2 \\[6pt]
 \text{s.t.} \quad
 & \left[ \phi_p + \Bigl( \sum_{b \in L_b} X_{bp} \Bigr) + \Bigl( \sum_{u \in L_u} X_{up} \Bigr) \right] < 2 && \text{(HC1)} \\[6pt]
 & \left[ \Bigl( \sum_{b \in L_b} X_{b} \Bigr) + \Bigl( \sum_{u \in L_u} X_{u} \Bigr) \right] > 0 && \text{(HC2)} \\[4pt]
 & w_1 + w_2 = 1, \quad 0 \le w_k \le 1
 \end{aligned}
 \]
 </div>
 <p class="math-caption">HC1 allows a position to hold one logistic object or one obstacle, never both and never two. HC2 requires at least one item to be placed at all. A solution failing either scores zero; otherwise fitness lands between 0 and 1.</p>

 <p>HC1 is the constraint that makes this combinatorial. Every cell you spend is a cell you cannot spend on anything else, and the obstacle term means the grid's difficulty is set before the search starts. HC2 exists to rule out the empty solution, which would otherwise be feasible and uninformative.</p>

 <p>There is no flow conservation here, no belt capacity, no constraint saying a route has to connect. The underground pairing rule, the one that requires an entrance and exit in the same row or column, facing the same way, at most four cells apart, is described in the paper as a property of the game rather than written as an inequality.</p>

 <p>All of that is missing because the model does not evaluate solutions. The game does. A candidate is built in Factorio through the interface, run, and scored on what the receiving chest actually contains, so the physics stays where it already worked instead of being restated as constraints that could disagree with it. That is the paper's real trick, and it is why the formal model can afford to be this short.</p>

 <p>It also explains the weights. With \(w_2 = 0\) the objective would count only what arrives, and every layout that fails to connect would score identically at zero, leaving a search no way to tell a near miss from an empty grid. Because \(SC_2\) pays for items merely taken out of the input chest, a belt that starts correctly and dead-ends still earns something. That is a modelling decision rather than an algorithmic one, and it is the difference between a landscape a local search can climb and a cliff it cannot.</p>

 <h2>The niche</h2>

  <p>Optimisation research runs on benchmark sets, and benchmark sets have a known failure mode: they get old, methods get tuned against them, and after a decade you cannot tell whether a new result is progress or overfitting to a fixed library of instances. Games are an interesting escape from that, because a game that people play is a simulator someone else has already paid to build, validate and keep interesting.</p>

 <p>Factorio is a good one for the specific reason that its difficulty is combinatorial rather than reflexive. Nothing here depends on reaction time or hidden information, like many games: it is placement under constraints, with a throughput number at the end, which is the same shape as facility layout, circuit routing and warehouse design in real life. It also comes with something no synthetic benchmark has: a large population of humans who have independently optimised the same problem for fun, and whose solutions you can compare against.</p>

 <p>This is one problem inside one game, with a single objective and an evaluation function that a commercial studio can change in a patch. A benchmark you do not control is a benchmark that can be revised out from under your published results. And the community solutions that make it appealing are also a contamination risk, since a method tuned by someone who knows the good patterns is not discovering them. Games are a useful source of problems, not a replacement for the discipline of saying what your benchmark can and cannot show.</p>

 <div class="faq-section">
 <h2>Common questions</h2>

 <details class="faq-item">
 <summary>Is this really operational research, or just a game?</summary>
 <p>Why not both? Strip the graphics and what remains is a constrained routing problem: place components on a grid, respect adjacency and pairing rules, maximise throughput. The same integer program describes it whether the output is rendered as a conveyor or as a line in a warehouse plan. The game is the interface, not the problem.</p>
 </details>

 <details class="faq-item">
 <summary>Why not just solve it outright?</summary>
 <p>You can, up to a point. The small grids are well within reach of an exact solver, and where an exact answer is available it is the right thing to use, because it comes with a proof. The difficulty is that the search space grows with both the grid area and the number of things each cell could be, and the underground pairing constraint links cells that are not adjacent. Past a certain size you are choosing between a guaranteed answer you will not receive today and a good answer you can have in seconds, which is the situation metaheuristics exist for.</p>
 </details>

 <details class="faq-item">
 <summary>Does the demo run the real game?</summary>
 <p>The demo in this site does not. The paper drove the actual game through an interface and measured real throughput. The demo reimplements the belt mechanics in JavaScript, because running thousands of evaluations in front of you requires an evaluation that takes microseconds rather than seconds. The mechanics modelled here are the ones the problem turns on: belt direction, underground pairing and reach, and items moving at a fixed rate. Everything else about Factorio is absent.</p>
 </details>

 <details class="faq-item">
 <summary>Why is genetic programming so much better here?</summary>
 <p>Because its moves are the right size. Connect lays a whole run of belts in one operation, where annealing changes a cell and asks whether that helped, and most single-cell changes to a broken route help by nothing at all. Given the same budget of four thousand evaluations on the twelve by twelve grid, the population method connects on five runs out of five and reaches a fitness around 0.82, while annealing connects on three and sits near 0.66. The gap in tidiness is starker than the gap in score: annealing arrives with roughly 88 belts on the board against 28, because it has no move that removes a redundant detour once the route works. That's not to say annealing is a weak algorithm. It says the operator matters more than the acceptance rule, which is the same conclusion the paper reached and the reason the comparison is worth running at all.</p>
 </details>

 </div>

 <h2 class="section-heading" id="references">References</h2>

 <ol class="references">
 <li id="ref-1">
 Reid, K. N., Miralavy, I., Kelly, S., Banzhaf, W., &amp; Gondro, C. (2021). <a href="https://scholar.google.com/citations?view_op=view_citation&amp;hl=en&amp;user=bwl2u00AAAAJ&amp;citation_for_view=bwl2u00AAAAJ:8k81kl-MbHgC" target="_blank" rel="noopener noreferrer">The Factory Must Grow: Automation in Factorio</a>. <em>Proceedings of the Genetic and Evolutionary Computation Conference Companion</em>. <a href="https://arxiv.org/abs/2102.04871" target="_blank" rel="noopener">https://arxiv.org/abs/2102.04871</a>
 </li>
 </ol>

        

 ]]></content:encoded>
    </item>
    <item>
      <title>How the Interactive Posts Work</title>
      <link>https://www.kenreid.co.uk/blog/how-the-interactive-posts-work.html</link>
      <guid>https://www.kenreid.co.uk/blog/how-the-interactive-posts-work.html</guid>
      <pubDate>Sun, 16 Aug 2026 00:00:00 +0000</pubDate>
      <description>Several blogs I&#x27;ve written run simulations in your browser. The chassis underneath them: theme-reactive canvases, seeded randomness, phone-width rules, and a smoke test.</description>
      <category>technology</category>
      <content:encoded><![CDATA[

 <p>Someone on Cyberspace (new social media website that has such a cool vibe to it) recently asked me how I set up the simulators on my website, if I used a specific library. I responded saying how it's raw javascript, but that got me thinking: I intend to keep making these kind of educational and fun blogs, so why not make a little framework to make it easier on myself?</p>

 <p>The algorithms themselves need to be separate, I wasn't planning on making a new JMetal, DEAP or similar. Part of the series on <a href="https://www.kenreid.co.uk/series-how-this-site-is-built.html">how this site is built</a>.</p>

 <div class="plain-english-box">
 <h2>Quick jargon guide</h2>
 <ul>
 <li><strong>Canvas:</strong> an HTML element that gives JavaScript a rectangle of pixels to draw on, every frame, with no memory of what was there before.</li>
 <li><strong>devicePixelRatio:</strong> how many physical screen pixels sit behind one CSS pixel. Ignoring it can cause canvas drawings to come out blurry on phones and retina displays.</li>
 <li><strong>CSS custom properties:</strong> variables that live in stylesheets (<code>--viz-ink: #2b2b2b</code>). Scripts can read them at runtime, which is the hinge this whole system turns on.</li>
 <li><strong>MutationObserver:</strong> a browser API that fires a callback when part of the page changes, like an attribute being flipped by a theme toggle.</li>
 <li><strong>Seeded RNG:</strong> a random number generator that produces the same "random" sequence every time you start it from the same seed.</li>
 <li><strong>requestAnimationFrame:</strong> the browser's "call me before the next repaint" scheduler, the correct heartbeat for animation loops.</li>
 </ul>
 </div>

 <h2>The CSS owns the colours</h2>

 <p>I always enjoyed color theory in computer science. The RGB values, pixels (did you know it stands for "picture element"?), bitmaps vs JPEGs vs other formats, it's super interesting to me. On this website, the theme toggle cannot restyle a canvas the way it restyles text, and the first version of the ant post hardcoded its colours in JavaScript and looked wrong in one theme or the other. Every widget's colours live on its wrapper element as CSS custom properties, set for both themes in one place in the stylesheet, and the little framework, <code>js/kr-viz.js</code>, reads them and hands them to the drawing code as <code>ctx.colors</code>. Before it existed, every post carried its own copy of the function that reads them: sixteen copies, in five slightly different versions. Here's the one that's left, and the observer that rereads it:</p>

<pre><code class="language-javascript">var TOKENS = ['s1', 's2', 's3', 's4', 's5', 's6', 's7', 's8',
              'ink', 'muted', 'grid', 'surface', 'border', 'cell'];

function readColors(el) {
  var cs = getComputedStyle(el), out = {};
  for (var i = 0; i &lt; TOKENS.length; i++) {
    out[TOKENS[i]] = cs.getPropertyValue('--viz-' + TOKENS[i]).trim();
  }
  return out;
}

new MutationObserver(function () {
  colors = readColors(root);
  ctx.colors = colors;
  redraw();
}).observe(document.documentElement, {attributes: true, attributeFilter: ['data-theme']});</code></pre>

 <p>The observer redraws the moment <code>data-theme</code> gets toggled, so a running simulation changes palette mid-run. This has no practical value whatsoever. </p>

 <h2>Randomness must be reproducible</h2>

 <p><code>Math.random()</code> is banned from the demos. Every widget gets its randomness from <code>ctx.rng()</code>, which is mulberry32, a seeded generator inside the framework:</p>

<pre><code class="language-javascript">function mulberry32(seed) {
  var a = seed >>> 0;
  return function () {
    a |= 0; a = (a + 0x6D2B79F5) | 0;
    var t = Math.imul(a ^ (a >>> 15), 1 | a);
    t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
    return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
  };
}</code></pre>

 <p>When a reader reports that the race chart does something odd around 90,000 evaluations, I can watch the same 90,000 evaluations they did. "New cities" buttons just bump the seed. </p>

 <h2>Phones are the real test</h2>

 <p>No canvas ever gets a fixed rendered height now: the framework computes height from measured width, so narrow screens get a taller aspect instead of a squashed ribbon. It also caps the pixel density at 2, because a 3x phone would otherwise redraw nine pixels for every one it shows, every frame, for detail nobody can see at that size. Side-by-side panes stack vertically below ~480px, and sliders become full-width rows on small screens.</p>

 <p>A canvas only gets <code>touch-action: none</code> if it has drag interaction, so chart canvases never trap a reader mid-scroll.</p>

 <p>And because rules I merely remember are rules I eventually break, the <a href="https://www.kenreid.co.uk/blog/my-website-has-a-test-suite.html">test suite</a> enforces them. The smoke test auto-discovers every blog page containing a <code>&lt;canvas&gt;</code> tag (no registration list to forget to update) and loads each at 360px wide in a headless browser. It fails the build on horizontal overflow, a canvas wider than the viewport, a canvas squashed under 100px tall, or any console error.</p>

 <h2>The chassis checklist</h2>

 <p>What every new interactive post starts from:</p>

 <ul>
 <li>A <code>.kr-viz</code> wrapper with empty slots for the toolbar, the sliders and the stat tiles, which the framework fills. The <code>--viz-*</code> palette comes from the stylesheet; a post that wants other series colours sets them on its own wrapper, in both themes.</li>
 <li>One call: <code>KRViz.mount(target, {canvases, buttons, controls, stats, charts, init, step, draw})</code>. The post keeps <code>init</code>, <code>step</code> and <code>draw</code>, the algorithm and the drawing, as plain functions you can read in view-source. The framework does the rest: it sizes the canvases, rereads the colours on a theme flip, and runs the loop.</li>
 <li><code>ctx.rng()</code> for every random draw, from a seed in the options.</li>
 <li>Speed in steps per second, the same in every demo; the framework works out how many steps to run in each frame.</li>
 <li><code>role="img"</code> and a real <code>aria-label</code> on every canvas.</li>
 </ul>

 <div class="faq-section">
 <h2>Common questions</h2>

 <details class="faq-item">
 <summary>Why raw canvas instead of a chart library?</summary>
 <p>The demos ARE the content, and libraries make the common case easy at the price of making the odd case a bit more awkward. A four-way race chart with collision-nudged end labels, step-function lines, and a temperature strip sharing its x-axis is the odd case. </p>
 </details>

 <details class="faq-item">
 <summary>Don't the simulations drain phone batteries?</summary>
 <p>They start by themselves, but only run while they're on screen: the framework pauses a demo when it scrolls out of view and picks it up again when it comes back, unless you paused it yourself. If your system asks for reduced motion, they wait for you to press Play. They also do their work inside a requestAnimationFrame budget, so the browser throttles them in background tabs. </p>
 </details>

 <details class="faq-item">
 <summary>Why not SVG, which the theme could style directly?</summary>
 <p>SVG restyles beautifully but costs per element, and these simulations push thousands of points and segments per frame.</p>
 </details>
 </div>

        

 ]]></content:encoded>
    </item>
    <item>
      <title>Letting Robots Update Your Homepage</title>
      <link>https://www.kenreid.co.uk/blog/letting-robots-update-your-homepage.html</link>
      <guid>https://www.kenreid.co.uk/blog/letting-robots-update-your-homepage.html</guid>
      <pubDate>Fri, 14 Aug 2026 00:00:00 +0000</pubDate>
      <description>My homepage shows what I&#x27;m listening to and reading, updated every Monday by a GitHub Action that commits to its own repository. Live data on a static site, no server required.</description>
      <category>technology</category>
      <content:encoded><![CDATA[

 <p><a href="https://www.kenreid.co.uk/index.html">My homepage</a> shows my scrobble count, the book I'm reading through, the last song I played, and whatever I last posted on Bluesky. This is a static site, meaning it can't just run dynamic scripts on a whim. I set up a system to dynamically update my HTML weekly, however, and it works for free. Part of the series on <a href="https://www.kenreid.co.uk/series-how-this-site-is-built.html">how this site is built</a>.</p>

 <div class="plain-english-box">
 <h2>Quick jargon guide</h2>
 <ul>
 <li><strong>GitHub Actions:</strong> GitHub's free automation. You describe a job in a text file, and GitHub runs it on their machines, on a schedule if you like.</li>
 <li><strong>Cron:</strong> the venerable syntax for "run this at these times". Mine says <code>0 6 * * 1</code>: every Monday at 06:00 UTC.</li>
 <li><strong>API:</strong> a website's machine-readable front door. Last.fm's API answers "what did this user play?" with data instead of a web page.</li>
 <li><strong>API key / secret:</strong> the password for that front door, stored in GitHub's encrypted secrets so it never appears in the code.</li>
 <li><strong>Scrobble:</strong> one logged song play. My <a href="https://www.kenreid.co.uk/blog/what-50000-scrobbles-say-about-me.html">Last.fm account</a> has been counting since 2011.</li>
 <li><strong>Bot commit:</strong> a change to the repository made by an automation rather than a person.</li>
 </ul>
 </div>

 <h2>The idea: if the site can't fetch, fetch into the site</h2>

 <p>A normal website with live data has a server that queries things when visitors arrive. A static site can't do that, but it has a loophole: the site is a git repository, and <em>anything that can commit to the repository can change the site</em>. So instead of fetching data per visit, a scheduled job fetches it once a week and commits the results as plain JSON files. The site stays static. Visitors' browsers read those JSON files with an ordinary fetch: same origin, no keys, no rate limits, nothing that can fail separately from the site itself.</p>

 <p>The whole pipeline is one workflow file and three Python scripts. The workflow checks out the repository, runs the two fetch scripts, checks what they wrote, and commits whatever changed (the comments are trimmed out here):</p>

<pre><code class="language-yaml">name: Refresh live data

on:
  schedule:
    - cron: "0 6 * * 1"   # weekly, Mondays 06:00 UTC
  workflow_dispatch:

permissions:
  contents: write

concurrency:
  group: live-data
  cancel-in-progress: false

jobs:
  refresh:
    runs-on: ubuntu-latest
    timeout-minutes: 15
    env:
      FILES: >-
        data/lastfm.json data/now.json data/lastfm-history.json
        data/topalbums.json data/books.json data/reading.json
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-python@v5
        with:
          python-version: "3.12"

      - name: Fetch listening and currently-reading
        id: now
        continue-on-error: true
        env:
          LASTFM_API_KEY: ${{ secrets.LASTFM_API_KEY }}
        run: python .github/scripts/refresh_now.py

      - name: Refresh the read shelf
        id: books
        continue-on-error: true
        run: python .github/scripts/refresh_books.py

      - name: Validate the data files
        id: validate
        run: python .github/scripts/validate_data.py $FILES

      - name: Commit what refreshed
        if: always() &amp;&amp; steps.validate.outcome == 'success'
        run: |
          if git diff --quiet -- $FILES; then
            echo "No change."
            exit 0
          fi
          git config user.name "github-actions[bot]"
          git config user.email "github-actions[bot]@users.noreply.github.com"
          git add -- $FILES
          git commit -m "Refresh live data (Last.fm, Goodreads)"
          git pull --rebase origin "$GITHUB_REF_NAME"
          git push origin "HEAD:$GITHUB_REF_NAME"

      - name: Fail if any source did not refresh
        if: always()
        env:
          NOW: ${{ steps.now.outcome }}
          BOOKS: ${{ steps.books.outcome }}
          VALID: ${{ steps.validate.outcome }}
        run: |
          status=0
          if [ "$NOW" != success ]; then
            echo "::error::Last.fm / currently-reading refresh: $NOW (see 'Fetch listening and currently-reading')"
            status=1
          fi
          if [ "$BOOKS" != success ]; then
            echo "::error::Read-shelf refresh: $BOOKS (see 'Refresh the read shelf')"
            status=1
          fi
          if [ "$VALID" != success ]; then
            echo "::error::Data validation: $VALID; nothing was committed"
            status=1
          fi
          exit $status
</code></pre>

 <p>The <code>workflow_dispatch</code> line adds a manual "run now" button in GitHub's interface (which you will want the first dozen times), while the diff check means runs where nothing changed produce no commit at all.</p>

 <p>Most of the rest is there for when something breaks. Both fetch steps are allowed to fail (<code>continue-on-error</code>), so a Goodreads outage doesn't stop the Last.fm numbers from landing, and the last step turns the run red afterwards so I still hear about it. Nothing is committed unless every file passes its schema in <code>data/schema/</code>, because the pages read these files with no fallback for a wrong shape. The robot and I both push to the same branch, hence the <code>git pull --rebase</code> before its push, and the concurrency group makes a manual run wait for a scheduled one instead of both committing the same files at once.</p>

 <h2>What the script gathers</h2>

 <p>The first script, <code>refresh_now.py</code>, makes a handful of HTTP requests. From the Last.fm API: my total play count, the most recent track, and my top albums of the past three months, which feed the homepage stat, the "now playing" strip, and the record crate on <a href="https://www.kenreid.co.uk/music.html">the music page</a> respectively. From Goodreads, which shut its API years ago but still publishes RSS feeds: my currently-reading shelf, parsed straight out of the XML.</p>

 <p>The second, <code>refresh_books.py</code>, reads the feed of my read shelf the same way and merges it into <code>data/books.json</code>, so a book I mark as read on Goodreads turns up on this site's reading pages by itself. The feed only reports the latest time I finished a book, so when I re-read something the script holds on to the earlier dates itself, because they exist nowhere else. It also writes <code>data/reading.json</code>, the books-read and reviews figures on the homepage. Everything goes into JSON files under <code>data/</code>, most of them a few kilobytes, apart from <code>books.json</code>, which holds every book I've marked read and weighs about 83 KB.</p>

 <p>Each run appends that week's play count to a rolling log, capped at ninety entries, which at one measurement a week is close to two years of history. </p>

 <figure>
 <img src="https://www.kenreid.co.uk/img/photography/thumb/12.webp" alt="A brindle Staffordshire terrier standing alert in woodland, looking at the camera" loading="lazy" class="kr-figure-img" width="400" height="266" srcset="https://www.kenreid.co.uk/img/photography/thumb/12.webp 400w, https://www.kenreid.co.uk/img/photography/thumb/12@2x.webp 800w, https://www.kenreid.co.uk/img/photography/hero/12.webp 1200w" sizes="(min-width: 800px) 760px, 100vw">
 <figcaption class="figure-note"><span class="figure-copyright">&copy; Ken Reid. All rights reserved.</span> A good fetcher.</figcaption>
 </figure>

 <h2>What the homepage shows</h2>

 <p>The strip under the hero is a single row of up to four things, and only two of them come from the robot. The book and the track are read straight out of <code>now.json</code>. The latest short story comes from <code>stories.json</code>, which is an empty list until the first story goes up, so for now the strip leaves that card out. The latest blog post isn't in the strip at all, because the homepage has a whole section of its own for those.</p>

 <p>Last.fm distinguishes a track that is playing from one that was played, so the label switches between "Now playing" and "Last played" depending on whether I happen to be listening when you open the page, and the first version gets a little animated equaliser beside it. It is not accurate, because it is updated so infrequently, but it gives some flavor to the site and over time it's likely representative of me. Maybe.</p>

 <p>The third item, my most recent Bluesky post, is in no JSON file at all. Your browser fetches it from Bluesky's public API while the page loads. I've been tempted to remove it, as recently I've only posted new blogs to social media, but I hope I will return to posting random thoughts there again soon, making this a bit more useful than another place to see a new blog post (which is already on the index page!)</p>

 <h2>Design decisions </h2>

 <p>The homepage HTML contains a hardcoded scrobble count, and the JavaScript replaces it with the fresh value from the JSON after load. If the fetch fails, or JavaScript is off, the visitor sees a slightly stale number instead of a blank. The robot improves the site but it fails elegantly.</p>

 <p>Goodreads' RSS goes down more often than Last.fm's API. When a source fails, the script keeps whatever the previous run wrote rather than blanking it: better yesterday's truth than today's error. A failure in one source never stops the others from updating, of course.</p>

 <p>The Last.fm key lives in the repository's encrypted secrets and reaches the script as an environment variable. It appears in no file and no log for security reasons (not that I'd be massively put-out if someone broke into my goodreads or last.fm).</p>

 <p>The bot's commits are labelled as bot commits, touch only the data files, and say what they did. My git history has a weekly heartbeat in it now: a tidy row of "Refresh live data" commits, one every Monday, each one the robot clocking in so I don't have to manually update stuff.</p>

 <h2>What else this pattern is good for</h2>

 <p>Anything that changes slowly and comes from somewhere with an API or a feed: your latest posts elsewhere, sports scores, weather, stars on a project, prices you're tracking, a "days since" counter for whatever you are currently ashamed of. The recipe is always the same three steps: a script that fetches and writes JSON, a workflow that runs it on a schedule and commits, and a page that reads the JSON with a fallback. If the data changes faster than a schedule can keep up with (live chat, comment counts), a static site is the wrong tool and no robot can easily fix that. Everything slower works just fine though.</p>

 <div class="faq-section">
 <h2>Common questions</h2>

 <details class="faq-item">
 <summary>Doesn't committing data on a schedule bloat the repository?</summary>
 <p>Slowly, and acceptably. Each commit stores a few kilobytes of changed JSON, so a year of weekly commits comes to a couple of hundred kilobytes. </p>
 </details>

 <details class="faq-item">
 <summary>Why weekly instead of hourly, or on every visit?</summary>
 <p>I could but it's hardly vitally important and worth moving out of the free level of GitHub actions.</p>
 </details>

 <details class="faq-item">
 <summary>What happens when the robot itself breaks?</summary>
 <p>GitHub emails me when a scheduled workflow fails, and the last step makes sure a run where only one source failed still counts as a failure.</p>
 </details>

 <details class="faq-item">
 <summary>Could this update the page instantly when something changes?</summary>
 <p>Not this pattern. A schedule can only poll. Instant would need the source to push (webhooks) into something always listening, which drags a server back into the picture. </p>
 </details>
 </div>

        
 ]]></content:encoded>
    </item>
    <item>
      <title>Variable Neighbourhood Search, Live</title>
      <link>https://www.kenreid.co.uk/blog/variable-neighbourhood-search-live.html</link>
      <guid>https://www.kenreid.co.uk/blog/variable-neighbourhood-search-live.html</guid>
      <pubDate>Wed, 12 Aug 2026 00:00:00 +0000</pubDate>
      <description>An interactive variable neighbourhood search solving the Travelling Salesman Problem in your browser: watch a tour untangle itself, then watch the search escape a local optimum by changing what counts as a nearby move.</description>
      <category>ai</category>
    </item>
    <item>
      <title>Particle Swarm, Live</title>
      <link>https://www.kenreid.co.uk/blog/particle-swarm-live.html</link>
      <guid>https://www.kenreid.co.uk/blog/particle-swarm-live.html</guid>
      <pubDate>Tue, 11 Aug 2026 00:00:00 +0000</pubDate>
      <description>An interactive particle swarm optimiser in your browser: a flock of dots hunts a landscape none of them can see, steered by private memory and a single piece of gossip, and the sliders let you collapse the swarm on purpose.</description>
      <category>ai</category>
    </item>
    <item>
      <title>50 Blogs: An Infographic</title>
      <link>https://www.kenreid.co.uk/blog/50-blogs-an-infographic.html</link>
      <guid>https://www.kenreid.co.uk/blog/50-blogs-an-infographic.html</guid>
      <pubDate>Sat, 08 Aug 2026 00:00:00 +0000</pubDate>
      <description>Fifty posts in: an infographic the site drew about itself, and what it taught me. Words written, what gets read versus what I love writing, and the 135 drafts waiting their turn.</description>
      <category>personal</category>
    </item>
    <item>
      <title>Dark Mode That Doesn&#x27;t Flash</title>
      <link>https://www.kenreid.co.uk/blog/dark-mode-that-doesnt-flash.html</link>
      <guid>https://www.kenreid.co.uk/blog/dark-mode-that-doesnt-flash.html</guid>
      <pubDate>Thu, 06 Aug 2026 00:00:00 +0000</pubDate>
      <description>Dark mode is easy; dark mode without a white flash is more difficult. How this site stamps the theme before the first paint, and drags every chart and iframe along with it.</description>
      <category>technology</category>
    </item>
    <item>
      <title>Simulated Annealing, Live</title>
      <link>https://www.kenreid.co.uk/blog/simulated-annealing-live.html</link>
      <guid>https://www.kenreid.co.uk/blog/simulated-annealing-live.html</guid>
      <pubDate>Wed, 05 Aug 2026 00:00:00 +0000</pubDate>
      <description>An interactive introduction to simulated annealing: watch a ball escape local minima as you control the temperature, then race four algorithms live on the Travelling Salesman Problem in your browser.</description>
      <category>ai</category>
    </item>
    <item>
      <title>Evolution, Live</title>
      <link>https://www.kenreid.co.uk/blog/evolution-live.html</link>
      <guid>https://www.kenreid.co.uk/blog/evolution-live.html</guid>
      <pubDate>Tue, 04 Aug 2026 00:00:00 +0000</pubDate>
      <description>A genetic algorithm evolving a solution live in your browser: populations, crossover, mutation, and selection as sliders you can break. Fifty translucent triangles evolve the alphabet, and you supply the selection pressure.</description>
      <category>ai</category>
    </item>
    <item>
      <title>The Invisible Half of a Blog Post</title>
      <link>https://www.kenreid.co.uk/blog/invisible-half-of-a-blog-post.html</link>
      <guid>https://www.kenreid.co.uk/blog/invisible-half-of-a-blog-post.html</guid>
      <pubDate>Tue, 04 Aug 2026 00:00:00 +0000</pubDate>
      <description>Every post on this blog carries a pile of metadata nobody reads: social preview cards, structured data, feeds, citation tags. What each layer does, and why the real problem is discipline, not difficulty.</description>
      <category>technology</category>
      <category>writing</category>
    </item>
    <item>
      <title>Ctrl+K for a Static Site</title>
      <link>https://www.kenreid.co.uk/blog/ctrl-k-for-a-static-site.html</link>
      <guid>https://www.kenreid.co.uk/blog/ctrl-k-for-a-static-site.html</guid>
      <pubDate>Mon, 03 Aug 2026 00:00:00 +0000</pubDate>
      <description>Press Ctrl+K anywhere on this site and a command palette appears: every page, every post, every photo tag, searchable from the keyboard. Vanilla JavaScript, no library, and the whole thing is in the post to steal.</description>
      <category>technology</category>
    </item>
    <item>
      <title>The Paradox of Tolerance</title>
      <link>https://www.kenreid.co.uk/blog/the-paradox-of-tolerance.html</link>
      <guid>https://www.kenreid.co.uk/blog/the-paradox-of-tolerance.html</guid>
      <pubDate>Sun, 02 Aug 2026 00:00:00 +0000</pubDate>
      <description>Nazi salutes at Glasgow Green, a mile from the statue honouring Glaswegians who fought Franco. Karl Popper saw this coming in 1945: unlimited tolerance destroys itself. What the paradox actually says, what it doesn&#x27;t, and why it matters in Scotland right now.</description>
      <category>philosophy</category>
    </item>
    <item>
      <title>The Fermi Paradox - Why are we alone in space?</title>
      <link>https://www.kenreid.co.uk/blog/the-fermi-paradox-and-the-great-silence.html</link>
      <guid>https://www.kenreid.co.uk/blog/the-fermi-paradox-and-the-great-silence.html</guid>
      <pubDate>Tue, 28 Jul 2026 00:00:00 +0000</pubDate>
      <description>The galaxy is old and vast and should be teeming, and it is silent. A tour of the Fermi paradox&#x27;s unsettling answers through the science fiction that dramatises them, from Liu Cixin&#x27;s dark forest to Banks&#x27;s Culture.</description>
      <category>science</category>
      <category>books</category>
    </item>
    <item>
      <title>A Blog Engine in One JSON File</title>
      <link>https://www.kenreid.co.uk/blog/blog-engine-in-one-json-file.html</link>
      <guid>https://www.kenreid.co.uk/blog/blog-engine-in-one-json-file.html</guid>
      <pubDate>Sun, 26 Jul 2026 00:00:00 +0000</pubDate>
      <description>No database, no CMS, no Jekyll: this blog&#x27;s search, filters, pagination, read times, and related posts all hang off one JSON file and the Python scripts that run when I publish.</description>
      <category>technology</category>
      <category>writing</category>
    </item>
    <item>
      <title>Ant Colony, Live</title>
      <link>https://www.kenreid.co.uk/blog/ant-colony-live.html</link>
      <guid>https://www.kenreid.co.uk/blog/ant-colony-live.html</guid>
      <pubDate>Sun, 19 Jul 2026 00:00:00 +0000</pubDate>
      <description>An interactive ant colony optimiser in your browser: watch a shortest path emerge from thousands of tiny, blind, local decisions, and see what evaporation is really for.</description>
      <category>ai</category>
      <category>data science</category>
    </item>
    <item>
      <title>My Website Has a Test Suite</title>
      <link>https://www.kenreid.co.uk/blog/my-website-has-a-test-suite.html</link>
      <guid>https://www.kenreid.co.uk/blog/my-website-has-a-test-suite.html</guid>
      <pubDate>Fri, 17 Jul 2026 00:00:00 +0000</pubDate>
      <description>A Python audit script and headless browser smoke tests run on every push. Here&#x27;s why a personal blog has a test suite.</description>
      <category>technology</category>
    </item>
    <item>
      <title>My Teacher Said I Shouldn&#x27;t Go To University</title>
      <link>https://www.kenreid.co.uk/blog/my-teacher-said-i-shouldnt-go-to-university.html</link>
      <guid>https://www.kenreid.co.uk/blog/my-teacher-said-i-shouldnt-go-to-university.html</guid>
      <pubDate>Thu, 16 Jul 2026 00:00:00 +0000</pubDate>
      <description>My high school music teacher told me not to aim for university: I was &#x27;more suited to manual labour&#x27;.</description>
      <category>personal</category>
      <category>advice</category>
    </item>
    <item>
      <title>Free Comments via GitHub Discussions</title>
      <link>https://www.kenreid.co.uk/blog/free-comments-via-github-discussions.html</link>
      <guid>https://www.kenreid.co.uk/blog/free-comments-via-github-discussions.html</guid>
      <pubDate>Wed, 15 Jul 2026 00:00:00 +0000</pubDate>
      <description>How giscus gives a static site free comments with no database, no ads, and no tracking.</description>
      <category>technology</category>
    </item>
    <item>
      <title>Putting My Photos on a Map</title>
      <link>https://www.kenreid.co.uk/blog/putting-my-photos-on-a-map.html</link>
      <guid>https://www.kenreid.co.uk/blog/putting-my-photos-on-a-map.html</guid>
      <pubDate>Tue, 14 Jul 2026 00:00:00 +0000</pubDate>
      <description>264 of my photographs plotted on an interactive world map, this is how the photo map works.</description>
      <category>technology</category>
      <category>photography</category>
    </item>
    <item>
      <title>Self-Hosting Your Fonts</title>
      <link>https://www.kenreid.co.uk/blog/self-hosting-your-fonts.html</link>
      <guid>https://www.kenreid.co.uk/blog/self-hosting-your-fonts.html</guid>
      <pubDate>Sun, 12 Jul 2026 00:00:00 +0000</pubDate>
      <description>No Google Fonts, no third-party requests, and icon fonts subset from 75 KB down to 1.5 KB. Here&#x27;s how, and why it&#x27;s worth an afternoon.</description>
      <category>technology</category>
    </item>
    <item>
      <title>Reading This Site Offline</title>
      <link>https://www.kenreid.co.uk/blog/reading-this-site-offline.html</link>
      <guid>https://www.kenreid.co.uk/blog/reading-this-site-offline.html</guid>
      <pubDate>Sun, 12 Jul 2026 00:00:00 +0000</pubDate>
      <description>This site now works offline: one hand-written service worker, no framework, no build step. Here&#x27;s how.</description>
      <category>technology</category>
    </item>
    <item>
      <title>The Abandoned Places I Photograph</title>
      <link>https://www.kenreid.co.uk/blog/abandoned-places-i-photograph.html</link>
      <guid>https://www.kenreid.co.uk/blog/abandoned-places-i-photograph.html</guid>
      <pubDate>Sun, 12 Jul 2026 00:00:00 +0000</pubDate>
      <description>Why I photograph abandoned places and the ethics of pointing a camera at decay.</description>
      <category>photography</category>
      <category>personal</category>
    </item>
    <item>
      <title>Why I Love LaTeX</title>
      <link>https://www.kenreid.co.uk/blog/why-i-love-latex.html</link>
      <guid>https://www.kenreid.co.uk/blog/why-i-love-latex.html</guid>
      <pubDate>Sun, 12 Jul 2026 00:00:00 +0000</pubDate>
      <description>I wrote my thesis, my papers, and my CV in LaTeX, and I&#x27;d have it no other way.</description>
      <category>technology</category>
      <category>writing</category>
    </item>
    <item>
      <title>What 50,000 Scrobbles Say About Me</title>
      <link>https://www.kenreid.co.uk/blog/what-50000-scrobbles-say-about-me.html</link>
      <guid>https://www.kenreid.co.uk/blog/what-50000-scrobbles-say-about-me.html</guid>
      <pubDate>Mon, 06 Jul 2026 00:00:00 +0000</pubDate>
      <description>Fifteen years of Last.fm data - what I listen to when I&#x27;m feeling joyful, depressed, doing work, working out or studying.</description>
      <category>personal</category>
      <category>music</category>
    </item>
    <item>
      <title>Hosting a Photography Portfolio on GitHub for Free</title>
      <link>https://www.kenreid.co.uk/blog/hosting-photography-on-github-for-free.html</link>
      <guid>https://www.kenreid.co.uk/blog/hosting-photography-on-github-for-free.html</guid>
      <pubDate>Mon, 06 Jul 2026 00:00:00 +0000</pubDate>
      <description>GitHub Pages caps your site at 1 GB, and my photography portfolio is bigger than that on its own. Here&#x27;s the free workaround: thumbnails in the repo, full-size originals in a GitHub release.</description>
      <category>photography</category>
      <category>technology</category>
    </item>
    <item>
      <title>Why You Aren&#x27;t a &quot;Visual Learner&quot;</title>
      <link>https://www.kenreid.co.uk/blog/why-you-arent-a-visual-learner.html</link>
      <guid>https://www.kenreid.co.uk/blog/why-you-arent-a-visual-learner.html</guid>
      <pubDate>Sat, 04 Jul 2026 00:00:00 +0000</pubDate>
      <description>Everyone took the quiz and got a label. Decades of cognitive psychology say the label does nothing: matching teaching to &#x27;learning styles&#x27; doesn&#x27;t improve learning, and believing it limits you. What works instead is effortful, unglamorous, and the same for everyone.</description>
      <category>philosophy</category>
      <category>science</category>
    </item>
    <item>
      <title>Frodo, Sam, and love.</title>
      <link>https://www.kenreid.co.uk/blog/frodo-sam-and-love.html</link>
      <guid>https://www.kenreid.co.uk/blog/frodo-sam-and-love.html</guid>
      <pubDate>Thu, 02 Jul 2026 00:00:00 +0000</pubDate>
      <description>A queer-friendly reading of Frodo and Sam: not a hidden romance, but male friendship deep enough for tears, touch, and total trust. Insisting that tenderness must be romance rebuilds the very wall this story tears down. (Gimli and Legolas, on the other hand...)</description>
      <category>books</category>
      <category>philosophy</category>
      <category>personal</category>
    </item>
    <item>
      <title>Optimizing Your Schedule II (or: When Hours Aren&#x27;t the Thing You Run Out Of)</title>
      <link>https://www.kenreid.co.uk/blog/optimizing-your-schedule-ii.html</link>
      <guid>https://www.kenreid.co.uk/blog/optimizing-your-schedule-ii.html</guid>
      <pubDate>Tue, 30 Jun 2026 00:00:00 +0000</pubDate>
      <description>A reply to Part I: time is not the only budget. Energy is a second, renewable resource that some activities spend and others restore. A working model of a depleted week, and why rest is production, not indulgence.</description>
      <category>personal</category>
      <category>data science</category>
      <category>advice</category>
    </item>
    <item>
      <title>What We Leave Behind</title>
      <link>https://www.kenreid.co.uk/blog/what-we-leave-behind.html</link>
      <guid>https://www.kenreid.co.uk/blog/what-we-leave-behind.html</guid>
      <pubDate>Sat, 27 Jun 2026 00:00:00 +0000</pubDate>
      <description>From the plutonium in the planet&#x27;s crust to the Golden Record in interstellar space to the memory of a Tuesday coffee, a look at everything we leave behind, and why none of us ever truly vanishes.</description>
      <category>personal</category>
      <category>philosophy</category>
      <category>science</category>
    </item>
    <item>
      <title>Optimizing Your Schedule (or: Treating Your Week Like a Solvable Problem)</title>
      <link>https://www.kenreid.co.uk/blog/optimizing-your-schedule.html</link>
      <guid>https://www.kenreid.co.uk/blog/optimizing-your-schedule.html</guid>
      <pubDate>Mon, 15 Jun 2026 00:00:00 +0000</pubDate>
      <description>What an operations researcher actually means by optimizing a schedule: objectives, constraints and weightings, a values clarification grid, and a working integer-programming model of your week. Nerd mode optional.</description>
      <category>personal</category>
      <category>data science</category>
      <category>advice</category>
    </item>
    <item>
      <title>What Was I Made For?</title>
      <link>https://www.kenreid.co.uk/blog/what-was-i-made-for.html</link>
      <guid>https://www.kenreid.co.uk/blog/what-was-i-made-for.html</guid>
      <pubDate>Fri, 12 Jun 2026 00:00:00 +0000</pubDate>
      <description>On finite time, micro-ambition, Gandalf, Tim Minchin, and why every &#x27;one day&#x27; dream needs a plan or it disappears.</description>
      <category>personal</category>
      <category>advice</category>
    </item>
    <item>
      <title>M*A*S*H in the Modern Era: Comedy, Trauma, and the 4077th</title>
      <link>https://www.kenreid.co.uk/blog/mash-modern-perspective.html</link>
      <guid>https://www.kenreid.co.uk/blog/mash-modern-perspective.html</guid>
      <pubDate>Mon, 01 Jun 2026 00:00:00 +0000</pubDate>
      <description>A modern look at M*A*S*H: how a Korean War sitcom from the 1970s remains one of the sharpest critiques of bureaucracy, war, and trauma in television history.</description>
      <category>television</category>
    </item>
    <item>
      <title>Iain M. Banks and The Culture</title>
      <link>https://www.kenreid.co.uk/blog/the-culture-series.html</link>
      <guid>https://www.kenreid.co.uk/blog/the-culture-series.html</guid>
      <pubDate>Mon, 01 Jun 2026 00:00:00 +0000</pubDate>
      <description>Smashing the world requires only a hammer; building a better one requires imagination. A reflection on Iain M. Banks&#x27;s Culture series and the contrarian act of imagining a future that works.</description>
      <category>books</category>
    </item>
    <item>
      <title>The Individualization of Responsibility</title>
      <link>https://www.kenreid.co.uk/blog/individualization-of-responsibility.html</link>
      <guid>https://www.kenreid.co.uk/blog/individualization-of-responsibility.html</guid>
      <pubDate>Wed, 27 May 2026 00:00:00 +0000</pubDate>
      <description>Corporations reframe systemic crises as consumer morality plays. From plastic bags to five-minute showers, how the public ends up feeling guilty while industrial actors avoid regulation.</description>
      <category>philosophy</category>
    </item>
    <item>
      <title>Advice to My Younger Self</title>
      <link>https://www.kenreid.co.uk/blog/advice-to-my-younger-self.html</link>
      <guid>https://www.kenreid.co.uk/blog/advice-to-my-younger-self.html</guid>
      <pubDate>Mon, 25 May 2026 00:00:00 +0000</pubDate>
      <description>Fifty things I would tell my younger self, from pensions and emergency funds to sunscreen, boundaries, and uncomfortable conversations.</description>
      <category>personal</category>
    </item>
    <item>
      <title>The Books I Recommend to Friends</title>
      <link>https://www.kenreid.co.uk/blog/books-i-recommend-to-friends.html</link>
      <guid>https://www.kenreid.co.uk/blog/books-i-recommend-to-friends.html</guid>
      <pubDate>Fri, 22 May 2026 00:00:00 +0000</pubDate>
      <description>The thirteen books I actually push on friends, grouped by the kind of friend I&#x27;d press them into the hands of.</description>
      <category>books</category>
    </item>
    <item>
      <title>Why Nobody Thanks the Person Who Stopped the Disaster</title>
      <link>https://www.kenreid.co.uk/blog/prevention-of-failure-is-unseen.html</link>
      <guid>https://www.kenreid.co.uk/blog/prevention-of-failure-is-unseen.html</guid>
      <pubDate>Thu, 21 May 2026 00:00:00 +0000</pubDate>
      <description>We celebrate the people who fix disasters and take preventive work for granted. That&#x27;s a bug in how we think, and it costs us.</description>
      <category>philosophy</category>
    </item>
    <item>
      <title>When I Ended Up in Italy Because of Mistaken Identity</title>
      <link>https://www.kenreid.co.uk/blog/mistaken-identity-italy.html</link>
      <guid>https://www.kenreid.co.uk/blog/mistaken-identity-italy.html</guid>
      <pubDate>Tue, 19 May 2026 00:00:00 +0000</pubDate>
      <description>How a mix-up over an email address sent me to the northern Italian mountains to photograph a tech event I had no business being at.</description>
      <category>photography</category>
      <category>personal</category>
    </item>
    <item>
      <title>How to Write a Blog (or: What I Learned By Doing It)</title>
      <link>https://www.kenreid.co.uk/blog/how-to-write-a-blog.html</link>
      <guid>https://www.kenreid.co.uk/blog/how-to-write-a-blog.html</guid>
      <pubDate>Wed, 13 May 2026 00:00:00 +0000</pubDate>
      <description>What I learned about writing a blog: why the word itself put me off, how I found a process, and what to do when people hate your work.</description>
      <category>personal</category>
      <category>writing</category>
      <category>advice</category>
    </item>
    <item>
      <title>The Hidden Cost of Cobalt</title>
      <link>https://www.kenreid.co.uk/blog/hidden-cost-of-cobalt-congo.html</link>
      <guid>https://www.kenreid.co.uk/blog/hidden-cost-of-cobalt-congo.html</guid>
      <pubDate>Wed, 13 May 2026 00:00:00 +0000</pubDate>
      <description>The batteries in our phones, laptops, and EVs are largely made of cobalt mined in the Democratic Republic of the Congo, often by hand and often by children. A reflection on a presentation I gave, and what we can actually do about it.</description>
      <category>philosophy</category>
      <category>technology</category>
    </item>
    <item>
      <title>ISO 8601 and the Date Cult I Happily Joined</title>
      <link>https://www.kenreid.co.uk/blog/iso-8601-date-cult.html</link>
      <guid>https://www.kenreid.co.uk/blog/iso-8601-date-cult.html</guid>
      <pubDate>Tue, 12 May 2026 00:00:00 +0000</pubDate>
      <description>An ode to ISO 8601 (YYYY-MM-DD) as the only sortable, unambiguous, international date format worth using.</description>
      <category>technology</category>
    </item>
    <item>
      <title>Accessibility-First Design in Data Science</title>
      <link>https://www.kenreid.co.uk/blog/accessibility-first-product-design-data-science.html</link>
      <guid>https://www.kenreid.co.uk/blog/accessibility-first-product-design-data-science.html</guid>
      <pubDate>Mon, 11 May 2026 00:00:00 +0000</pubDate>
      <description>Data science outputs are often inaccessible by default. Accessibility-first design in plots, dashboards, and tables improves understanding for everyone.</description>
      <category>data science</category>
    </item>
    <item>
      <title>The Life and Death of the Early Internet</title>
      <link>https://www.kenreid.co.uk/blog/life-and-death-of-the-early-internet.html</link>
      <guid>https://www.kenreid.co.uk/blog/life-and-death-of-the-early-internet.html</guid>
      <pubDate>Wed, 06 May 2026 00:00:00 +0000</pubDate>
      <description>From CRT monitors and Comet Cursor to MSN Messenger, Kazaa, Warcraft 3 forums, and Bebo: a personal history of how the early internet shaped who we are.</description>
      <category>personal</category>
    </item>
    <item>
      <title>From Scotland to Michigan</title>
      <link>https://www.kenreid.co.uk/blog/from-scotland-to-michigan.html</link>
      <guid>https://www.kenreid.co.uk/blog/from-scotland-to-michigan.html</guid>
      <pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate>
      <description>From Scotland to Michigan: a personal account of culture shock, free education, healthcare, xenophobia, weather, and what it means to belong somewhere new.</description>
      <category>personal</category>
    </item>
    <item>
      <title>The Ethics of LLM Use (Not LLMs)</title>
      <link>https://www.kenreid.co.uk/blog/ethics-of-llm-use-not-llms.html</link>
      <guid>https://www.kenreid.co.uk/blog/ethics-of-llm-use-not-llms.html</guid>
      <pubDate>Wed, 29 Apr 2026 00:00:00 +0000</pubDate>
      <description>An AI researcher&#x27;s unvarnished take on LLM ethics: the energy numbers, the cobalt hypocrisy, the Jevons paradox, and why telling people not to use AI on environmental grounds is a privilege position.</description>
      <category>ai</category>
    </item>
    <item>
      <title>Dungeon Crawler Carl and the Strange Dignity of LitRPG</title>
      <link>https://www.kenreid.co.uk/blog/dungeon-crawler-carl-litrpg-dignity.html</link>
      <guid>https://www.kenreid.co.uk/blog/dungeon-crawler-carl-litrpg-dignity.html</guid>
      <pubDate>Sat, 25 Apr 2026 00:00:00 +0000</pubDate>
      <description>A book snob&#x27;s confession: how Dungeon Crawler Carl used absurd LitRPG mechanics to deliver one of the most devastating emotional payloads in modern fantasy.</description>
      <category>books</category>
    </item>
    <item>
      <title>Fifteen Years of Silence: Patrick Rothfuss, The Doors of Stone, and the Architecture of Creative Friction</title>
      <link>https://www.kenreid.co.uk/blog/fifteen-years-of-silence-rothfuss-doors-of-stone.html</link>
      <guid>https://www.kenreid.co.uk/blog/fifteen-years-of-silence-rothfuss-doors-of-stone.html</guid>
      <pubDate>Wed, 22 Apr 2026 00:00:00 +0000</pubDate>
      <description>Fifteen years since The Wise Man&#x27;s Fear and still no Doors of Stone. A personal reflection on the Kingkiller Chronicle, the long wait, the charity debacle, and why writing at this level is brutally hard.</description>
      <category>books</category>
    </item>
    <item>
      <title>No Idea? No Problem: A Beginner&#x27;s Guide to Building Your Data Science Portfolio</title>
      <link>https://www.kenreid.co.uk/blog/no-idea-no-problem-data-science-portfolio.html</link>
      <guid>https://www.kenreid.co.uk/blog/no-idea-no-problem-data-science-portfolio.html</guid>
      <pubDate>Mon, 20 Apr 2026 00:00:00 +0000</pubDate>
      <description>A practical guide to building a data science portfolio when you have no idea where to start. Covers project types, documentation, novelty, collaboration, and working in the GenAI era.</description>
      <category>data science</category>
    </item>
    <item>
      <title>Evolutionary Computation&#x27;s Identity Crisis in the Age of GenAI</title>
      <link>https://www.kenreid.co.uk/blog/evolutionary-computation-identity-crisis.html</link>
      <guid>https://www.kenreid.co.uk/blog/evolutionary-computation-identity-crisis.html</guid>
      <pubDate>Sat, 18 Apr 2026 00:00:00 +0000</pubDate>
      <description>Evolutionary Computation is having an identity crisis in the GenAI era, despite delivering major real-world wins in engineering, logistics, and constrained optimization.</description>
      <category>ai</category>
    </item>
    <item>
      <title>In Defense of Audiobooks</title>
      <link>https://www.kenreid.co.uk/blog/in-defense-of-audiobooks.html</link>
      <guid>https://www.kenreid.co.uk/blog/in-defense-of-audiobooks.html</guid>
      <pubDate>Fri, 17 Apr 2026 00:00:00 +0000</pubDate>
      <description>The stigma against audiobooks is rooted in bad assumptions about learning, intelligence, and what counts as reading. The research doesn&#x27;t support any of it.</description>
      <category>books</category>
    </item>
    <item>
      <title>Why It Still Matters to Learn to Code in the Age of AI</title>
      <link>https://www.kenreid.co.uk/blog/why-learn-to-code-age-of-ai.html</link>
      <guid>https://www.kenreid.co.uk/blog/why-learn-to-code-age-of-ai.html</guid>
      <pubDate>Thu, 16 Apr 2026 00:00:00 +0000</pubDate>
      <description>Coding still matters because the value is not syntax memorization. It is learning to break down problems, reason across systems, and build reliable solutions.</description>
      <category>ai</category>
    </item>
    <item>
      <title>Your Professional Second Brain for Local LLM Work</title>
      <link>https://www.kenreid.co.uk/blog/second-brain-local-llm-professional.html</link>
      <guid>https://www.kenreid.co.uk/blog/second-brain-local-llm-professional.html</guid>
      <pubDate>Wed, 15 Apr 2026 00:00:00 +0000</pubDate>
      <description>A practical system for project documentation, knowledge management, and local LLM workflows that improves delivery, reviews, and project re-entry.</description>
      <category>ai</category>
    </item>
    <item>
      <title>Snowball vs Avalanche: The Science of Paying Off Debt</title>
      <link>https://www.kenreid.co.uk/blog/snowball-vs-avalanche.html</link>
      <guid>https://www.kenreid.co.uk/blog/snowball-vs-avalanche.html</guid>
      <pubDate>Tue, 14 Apr 2026 00:00:00 +0000</pubDate>
      <description>Snowball and avalanche both work, but for different reasons. A practical guide with simulator-backed charts on interest, timing, and motivation.</description>
      <category>finance</category>
    </item>
    <item>
      <title>Book Ratings Are Broken</title>
      <link>https://www.kenreid.co.uk/blog/rating-systems.html</link>
      <guid>https://www.kenreid.co.uk/blog/rating-systems.html</guid>
      <pubDate>Sun, 12 Apr 2026 00:00:00 +0000</pubDate>
      <description>569 books rated, and the star system fails at all of them. A data-driven look at my own Goodreads ratings, review lengths, genre bias, and the silence problem.</description>
      <category>books</category>
    </item>
    <item>
      <title>Why Most Self-Help Books Are Trash (And Which Ones Aren&#x27;t)</title>
      <link>https://www.kenreid.co.uk/blog/self-help-books.html</link>
      <guid>https://www.kenreid.co.uk/blog/self-help-books.html</guid>
      <pubDate>Sat, 11 Apr 2026 00:00:00 +0000</pubDate>
      <description>The bad ones are blog posts stretched to 300 pages. The good ones cite their sources. Here&#x27;s how to tell the difference.</description>
      <category>books</category>
    </item>
    <item>
      <title>Building a Photo Tagging System with CLIP</title>
      <link>https://www.kenreid.co.uk/blog/photo-tagging-with-clip.html</link>
      <guid>https://www.kenreid.co.uk/blog/photo-tagging-with-clip.html</guid>
      <pubDate>Fri, 10 Apr 2026 00:00:00 +0000</pubDate>
      <description>How I used OpenCLIP to automatically classify 500+ photos into 10 categories in under a minute, zero API cost, running entirely on CPU.</description>
      <category>ai</category>
      <category>photography</category>
    </item>
  </channel>
</rss>
