<?xml version="1.0" encoding="UTF-8"?>
<rss  xmlns:atom="http://www.w3.org/2005/Atom" 
      xmlns:media="http://search.yahoo.com/mrss/" 
      xmlns:content="http://purl.org/rss/1.0/modules/content/" 
      xmlns:dc="http://purl.org/dc/elements/1.1/" 
      version="2.0">
<channel>
<title>johnowhitaker.dev</title>
<link>https://johnowhitaker.dev/</link>
<atom:link href="https://johnowhitaker.dev/index.xml" rel="self" type="application/rss+xml"/>
<description></description>
<generator>quarto-1.11.3</generator>
<lastBuildDate>Sun, 06 Sep 2026 00:00:00 GMT</lastBuildDate>
<item>
  <title>This Week’s Mini-Projects</title>
  <link>https://johnowhitaker.dev/posts/sept2026.html</link>
  <description><![CDATA[ 




<p>It’s been a week full of little projects in between meetings with some very interesting people. Rather than write them up individually, I’m going to keep this to a few pics w/ light commentary. Reach out if you want more info :)</p>
<p><img src="https://johnowhitaker.dev/posts/images/s_cover.png" class="img-fluid"></p>
<p>A neighbor unloaded some arcade buttons, and I set up a single controller in an old GPU box to try them out as controls for (nostalgia max) CONTRA. Fun to use! I actually CAD’d up some sheet metal cases to make a pair of arcade control boxes with SendCutSend, but chickened out on spending $100+ on this project just yet. That’s “employed” kinda spending territory, for now my box version works great :)</p>
<p><img src="https://johnowhitaker.dev/posts/images/s_arcade.png" class="img-fluid"></p>
<p>I found <a href="https://www.youtube.com/watch?v=WcBs_K19Q5g">this video</a> recording the sounds made as water flows from a tube out of a small opening. Some amazing noises! To figure out what was going on, I had codex crop out the tube, plot the height over time, and make a spectrogram. You can see different harmonics of the (lengthening) air culumn above the water ring out, always odd harmonics, triggered when one of the flow frequencies intersects and resonates with a harmonic of the column. Fascinating physics.</p>
<p><img src="https://johnowhitaker.dev/posts/images/s_water_height.png" class="img-fluid"> <a href="https://x.com/johnowhitaker/status/2095216481026121968">X post</a></p>
<p>I set some clear sunstones going in a kiln, with aluminium oxide doped with a little copper powder surrounding them. The goal is to drive the copper ions into the feldspar, and have them precipitate out to give some nice red coloration. We only did 48 hours at a conservative 1100C this time, so the effects are mostly surface-level, but I love the blue color some stones got when passing light through (red when not looking through). Will try a longer hotter run soon.</p>
<p><img src="https://johnowhitaker.dev/posts/images/s_sunstone.png" class="img-fluid"></p>
<p>With extra Al2O3 on hand, I ordered some chromium oxide to finally try making rubies! I borrowed my neighbor’s welder, set the current to ~70A, shaped some carbon rods to fit the electrode holders, and blasted some 0.5wt% Cr doped Al oxide with an arc. Result: lots of messy polychrystalline ruby, that glows a beautiful red under UV light.</p>
<p><img src="https://johnowhitaker.dev/posts/images/s_ruby.png" class="img-fluid"></p>
<p>Then a fun project on the AI &lt;=&gt; bio side yesterday, which I’ll probably polish and write up properly at some point: I trained an adapter to map text embeddings from Qwen Embed 4B into the same embedding space as used by the Horizyn 1 Protein/Reaction embedding model. This means you can query for matching reactions and proteins based on natural language descriptions of reactions, very fun!</p>
<p><img src="https://johnowhitaker.dev/posts/images/s_cin.png" class="img-fluid"></p>
<p>You can also check how well different descriptions match a protein - for example, here’s a lipase that also hydrolyses medium-length esters, vs an acetyltransferase, with a set of three vibe descriptions:</p>
<p><img src="https://johnowhitaker.dev/posts/images/s_comp.png" class="img-fluid"></p>
<p>Then I got nerd sniped into lots of ALIFE stuff! Met a cool alife researcher, who then added me to a few discords and convinced me to present my neural physarium stuff to some other alife nerds. We had a great chat, cool to see other people messing about with this stuff. It also finally motivated me to try fluoddity, a fantastic “physarium++’ sim with tons of cool behaviour.</p>
<p><img src="https://johnowhitaker.dev/posts/images/s_fluoddity.png" class="img-fluid"></p>
<p>Speaking of self-organising systems, I grew a biofilm with some fantastic irridescence! One of the bluest blues I’ve seen when the light was the right angle. Other rainbow colors too.</p>
<p><img src="https://johnowhitaker.dev/posts/images/s_biofilm.png" class="img-fluid"></p>
<p>I had astra show off its 3D chops by making a better version of our earring exchange that goes with our tiny tiny library. Worked first time, I sent a prompt and woke up to a physical object! What a world we live in…</p>
<p><img src="https://johnowhitaker.dev/posts/images/s_library.png" class="img-fluid"></p>
<p>Oh, and the DVD drive hacking project was stalled on deciding the firmware, but is now unblocked thanks to about 15 minutes with astra, amazing RE model! No pics yet but full hw control seems possible!</p>
<p>Anyway, those are some highlights of the week. I also spent lots of time messing about looking at plant alkaloids but that got its [own post.]. Lots of other little software + alife tests too, but most quick vibe projects that I don’t feel need publicity unless I expand on them more.</p>
<p>PS: A few extras a little older than a week:</p>
<p><a href="https://x.com/johnowhitaker/status/2093726642875294122?s=20">The gene gun works!</a> - video link <a href="https://www.youtube.com/watch?v=gRIMH3cx0iw">here</a></p>
<p>I did an hour long talk on ‘Open Character Training’ paper (giving models personality) with Alexis - stream replay <a href="https://x.com/alexisgallagher/status/2093417582187729106?s=20">here</a></p>
<p>OK - this post helps reduce the “things I need to write up pile”, although next week promises to fill it again. Until next post - cheers. -Johno</p>



 ]]></description>
  <category>misc</category>
  <category>mini-hw-projects</category>
  <category>mini-projects</category>
  <guid>https://johnowhitaker.dev/posts/sept2026.html</guid>
  <pubDate>Sun, 06 Sep 2026 00:00:00 GMT</pubDate>
</item>
<item>
  <title>Searching For Solanine in Nightshades</title>
  <link>https://johnowhitaker.dev/posts/solanum.html</link>
  <description><![CDATA[ 




<p>I came across some black nightshade berries (Solanum nigrum) on my morning walk, and thought I’d try to show why you shouldn’t eat the unripe fruits. The theory goes that these plants are full of glycoalkaloids like solanine (the same nasties in green potato peels) but that these are mostly gone when the berries are fully ripe. I expected a fairly easy extraction + chromatography experience, but ended up trying a lot more runs than expected and learning lots along the way :) The <a href="https://www.youtube.com/watch?v=O3ZzgCSGsEU">video</a> has my rambly next-day thoughts and some footage of the experiments, this post will collect the more structured info for future reference.</p>
<p>(Disclaimer: probably don’t eat any fruits unless you are sure you aren’t eating the similar-looking deadly nightshade, or a <a href="https://x.com/thebiologistisn/status/2095513782525034819">bush with high solanine even when ripe</a>.)</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://johnowhitaker.dev/posts/images/solanine_tlc.png" class="img-fluid figure-img"></p>
<figcaption>One of many attempts - although which blobs are which in this one is TBD!</figcaption>
</figure>
</div>
<section id="attempt-1" class="level2">
<h2 class="anchored" data-anchor-id="attempt-1">Attempt #1</h2>
<p>For a first attempt, I crushed a few ripe berries (A) in 3:1 ethanol:vinegar, spotted it out on a TLC plate, and ran it with 2:4:4 acetone:ethanol:ethyl acetate. Ditto for partially ripe berries (B), unripe (C) and leaves (D). The first run was way overloaded, and even repeats with smaller drops (just A, B, and C on a single plate to be thrifty) were a bit of a mess. You can see red-fluorescing chlorophyl, bright blue (phenolics, flavinols etc) some lots of other junk. The purple anthocyanins from the ripe berries were also visible, and various things stained yellow after a dip in some KMnO4 (with NaOH and K2CO3) stain.</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://johnowhitaker.dev/posts/images/solanum_tlc_1.png" class="img-fluid figure-img"></p>
<figcaption>First attempts, showing red and blue fluorescence under 365nm UV, quenching under 254nm, and staining with KMnO4. A&amp;B, C&amp;D, then ABC</figcaption>
</figure>
</div>
<p>The problem is that we’ve extracted all sorts of things - we need a way of excluding as much of this junk as possible so that we can see the glycoalkaloids we’re interested in. Plus, I think the TLC mobile phase needs to be more polar…</p>
</section>
<section id="take-2-and-3-and-4" class="level2">
<h2 class="anchored" data-anchor-id="take-2-and-3-and-4">Take 2 (and 3 and 4…)</h2>
<p>With some suprisingly helpful advice from Fable 5.1 (hooray for more nuanced bio filters!) I tried again with a more refined extraction protocol:</p>
<ul>
<li>Mash up the berries in some vinegar (~4% acetic acid)</li>
<li>Spin down and push the supernatant through a syringe filter to get it nice and clear</li>
<li>Bring the pH up to ~11. The vinegar buffers it for a while and then it shoots up, I overshot a few times. What’s cool is that you get a color change to watch this - yellow appears then fades as you add the NaOH until suddenly it stays. Fun doing titration at ~1ml scale haha.</li>
<li>Cool then spin down, leaving a little pellet at the bottom</li>
<li>Wash with water, spin down</li>
<li>Dissolve in ethanol (I tried plain and lightly acidified)</li>
</ul>
<p>The theory here is that we’re extracting the alkaloids in acid then crashing them out at high pH in their (mostly insoluble) free-base form, then washing away most of the salts, sugars etc that remain soluble before re-dissolving the target molecules in ethanol. Some proteins and things will crash out too at the high pH, but this way we (hopefully) have waaaay less junk to deal with.</p>
<p>I then ran spots of these extracts (switching to A=ripe, B=unripe and C=Bittersweet Nightshade, a different species that isn’t edible and should have lots of the target compounds) with various mobile phases, e.g.&nbsp;4:1 ethanol:water, or variants thereof with some ethyl acetate added (and sometimes vinegar) - e.g.&nbsp;5:3:2:1 EA:ethanol:water:vinegar.</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://johnowhitaker.dev/posts/images/solanum_tlc_2.png" class="img-fluid figure-img"></p>
<figcaption>Some hard-to-see bands with KMnO4 staining</figcaption>
</figure>
</div>
<p>Besides tweaking the mobile phase and extraction, I also experimented with different ways to get the compounds to show up nicely - since they aren’t flourescent, staining or charring were our bet bets. There is Dragendorff’s reagent, which stains alkaloids specifically, but I don’t have it. For the charring, I added some hydroponic pH DOWN (phosphoric acid plus a bunch of buffer salts and such) to ethanol. The salts crashed out. I’d dip the TLC plate in the phosphoric acid+ethanol then place it on a hotplate set to 180C and wait for spots to go brown.</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://johnowhitaker.dev/posts/images/solanum_tlc_3.png" class="img-fluid figure-img"></p>
<figcaption>A small subset of my attempts at imaging the results</figcaption>
</figure>
</div>
<p>This was one of the more frustrating parts - I’d be able to see faint smudges by eye, but filming them on the pure white background of the plate was extremely challenging! I ended up relying on image manipulation to make the spotches stand out. Worse, the KMnO4 stain fades fast and changes in real time! Still, eventually, with charring or staining or both, I was able to get a few variants with obvious blobs in B and C and fainter smudges in A - possible candidates.</p>
</section>
<section id="lessons-learned" class="level2">
<h2 class="anchored" data-anchor-id="lessons-learned">Lessons Learned</h2>
<ul>
<li>Nice to learn a little more about acid-base extraction</li>
<li>I got much better at doing consistent spots on the TLC plates (a micropipette set to 3ul helped a lot)</li>
<li>I learned that alkaloids get ‘smeary’ in TLC if they’re not fully protonated, or fully de-protonated. So, one usually adds a little ammonia to the mobile phase to keep things basic. I didn’t have, but made some with NaOH+NH4NO3 in a vial such that the ammonia vapor hung around in the TLC chamber; mixed results. OR, you can just acidify things, hence the vinegar in some runs :)</li>
<li>I learned from my <a href="https://x.com/johnowhitaker/status/2094868882112835836?s=20">X thread</a> discussion that you can just taste solanine, bro - and indeed, with some very careful tasting and the bittersweet nightshade as reference, I think I can pick it out, and not taste it in the ripe fruit</li>
<li>I learned the ripe fruit are pretty tasty (I only ate one) and some are already pretty well bred for low solanine, hopefully one day we get a nice domesticated variety that is extra safe and tasty to much.</li>
<li>I got to practice my (very rusty) chemistry, mixing up stock solutions and adjusting pH and making yellow chemistry E&amp;F references and somehow spilling NONE of the stain or nasty lye or anything!</li>
<li>Fable 5.1 (which came out that day) was suprisingly helpful - I tested it on this expecting instant bio refusal (we are extracting a toxin after all) but instead it was a great teacher.</li>
</ul>
</section>
<section id="mixed-results" class="level2">
<h2 class="anchored" data-anchor-id="mixed-results">Mixed results</h2>
<p>The first image in this post was the one from the day that I thought had the best chance of being what I wanted: some blobs in B and C that weren’t in A. But they were streaky and indistinct.</p>
<p><img src="https://johnowhitaker.dev/posts/images/solanum_uv_fl.png" class="img-fluid"></p>
<p>As I wrote this up, I figured I’d give this one more go: more concentrated spots, a fresh batch of phosphoric acid+eth with more acid, EA+eth+vinegar as the mobile phase. This got much better brown spots, further up. And, prompted by codex, I tried something with this and yesterday’s ones that I hadn’t before: UV <strong>after</strong> the acid+heat stage - and got some glow where there wasn’t before! So maybe those higher spots in some of the runs were the compounds after all? Except from the lit I expect them to have low Rf?</p>
<p>Anyway, I end this post as I ended the video: inconclusively. If we wanted to do this properly, we’d get some proper stain, and some ammonia, and some green potatoes as another reference, and run everything a little more carefully… but I’m about ready to be done with these berries for now - after all, this ‘quick test’ has already taken more than a day!</p>
<p>I hope you found the journey interesting :) LMK what mistakes I’ve made, I bet there are plenty.</p>
<p>until next time,</p>
<p>Johno</p>
<p>PS: Title may be misleading, apparently this (taxonomically complex and messy) group might have more solasonine and solamargine vs solanine, so “hunting for steroidal glycoalkaloids in nightshades” might be better given ambiguity about exact species/accession and target molecules haha.</p>


</section>

 ]]></description>
  <category>bio</category>
  <guid>https://johnowhitaker.dev/posts/solanum.html</guid>
  <pubDate>Thu, 03 Sep 2026 00:00:00 GMT</pubDate>
</item>
<item>
  <title>Timrrr - A Small Buzzer To Train Time Sense</title>
  <link>https://johnowhitaker.dev/mini-hw-projects/timrr.html</link>
  <description><![CDATA[ 




<p>This is a physical manifestation of a trick I came up with in college, for times when I find myself getting lost in flow a little too much. It buzzes every 5 minutes. Not annoying enough (for me) to be a problem, just enough of a notification to snap me out of it if I’m scrolling X or something, reminding me that time is passing.</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://johnowhitaker.dev/mini-hw-projects/images/timrr-400.gif" class="img-fluid figure-img"></p>
<figcaption>Prototype goes buzz and moves a little on the desk</figcaption>
</figure>
</div>
<p>It runs on a coin cell, and uses a very efficient little haptic motor for the short buzz. I estimate the battery life at months if not &gt;1y! (I’ll update this post when the test one stops buzzing every few seconds). This was a fun little test of how easy it can be to get something made with ‘vibe PCB design’ and overseas PCBA. 5 boards, assembled (apart from battery clip and motor, which I ordered separately) were $15 with a JLCPCB coupon, and design was nice and quick with codex doing most of the menial stuff (I had to take over for the layout and some final check). We did miss one mistake, two pins need to be bridged for programming to work haha.</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://johnowhitaker.dev/mini-hw-projects/images/timrrr_hw.png" class="img-fluid figure-img"></p>
<figcaption>The hardware</figcaption>
</figure>
</div>
<p>I can get this far lower profile! I’m considering a version that will work as a pendant or in a coin pocket, maybe running a small kickstarter to fund a production batch. But even this clunky version is pretty dinky - I have one in a 3D printed case that lives in my pocket whenever I remember, and sits on my desk while I work.</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://johnowhitaker.dev/mini-hw-projects/images/timrrr_case.png" class="img-fluid figure-img"></p>
<figcaption>The verson I carry, in a 3D printed case</figcaption>
</figure>
</div>
<p>I’ll be honest, it’s a little easy to misplace something like this and forget that it exists, since it doesn’t offer any flashy gamifications or whatever to keep you as a user. But when I remember to have one on me, it saves me from losing time to YouTube, X etc multiple times a day, while never noticeably intruding on the bits of life that actually matter.</p>
<p>Source code etc: https://github.com/johnowhitaker/timrrr</p>
<p>(Writeup dated Aug 27 but I haven’t done anything besides use it since I assembled the test units on 04/08)</p>



 ]]></description>
  <category>mini-hw-projects</category>
  <guid>https://johnowhitaker.dev/mini-hw-projects/timrr.html</guid>
  <pubDate>Thu, 27 Aug 2026 00:00:00 GMT</pubDate>
</item>
<item>
  <title>Neural Physarium</title>
  <link>https://johnowhitaker.dev/mini-projects/physarium.html</link>
  <description><![CDATA[ 




<p>I trained a multi-agent slime mold sim style thingee to collaboratively paint emojis. This post has a few more details, but you’ll want the <a href="https://x.com/johnowhitaker/status/2084041106367615235">Twitter/X thread</a> for the progression and the fancy videos too.</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://johnowhitaker.dev/mini-projects/images/chromatic_mycelium_96_states_compact.gif" class="img-fluid figure-img"></p>
<figcaption>Growing a mushroom, 96px</figcaption>
</figure>
</div>
<p>I first got excited about physarium simulations 5 years ago, when I coded a high-performance slime sim I called “DotSwarm” in WebGL. The patterns these simple agent-based simulations produce are mesmerizing. (<a href="https://www.youtube.com/watch?v=mnuwd_z9YGE">Video</a>, <a href="https://observablehq.com/@johnowhitaker/dotswarm-exploring-slime-mould-inspired-shaders">Observable Notebook</a>). The general premise is that you simulate ‘agents’ and a ‘substrate’. The agents each move along with some velocity, sensing the substrate ahead of with, say, 3 ‘sensors’ at different angles, and then choosing how much to turn based on some rule. The agents also deposit ‘pheremones’ to the substrate, typically modelled as a grid, and it is these patterns of pheremones that lead to emergent behaviour where agents being to follow eachother along highways etc, much like ants do. This leads to some lovely patterns:</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://johnowhitaker.dev/mini-projects/images/slime_ring.png" class="img-fluid figure-img"></p>
<figcaption>My old WebGL slime mold sim</figcaption>
</figure>
</div>
<p>Anyway, after my recent <a href="https://johnowhitaker.dev/misc/nca_games.html">explorations of neural cellular automata for games</a>, I got in touch with <span class="citation" data-cites="Esychology">@Esychology</span> and we challenged eachother to try to make a <strong>trainable</strong> physarium setup work. This is more challenging than NCAs - with the agents zipping about, it is very tricky to see how gradients could flow from an objective back to the weights of the agent ‘brains’ in a way that makes training possible! Long story short, I managed to get it somewhat working - the rest of this post will be my attempt at all the little tricks that went into it, and suggestions for how this could be taken further :)</p>
<p>You can also look at this <a href="https://github.com/johnowhitaker/physarium/blob/main/physarium_jax_colab.ipynb">jax notebook</a> that implements it end-to-end (AI generated for a demo I gave on this to some people at an org that likes Jax), or check out the rest of the vibed code in the <a href="https://github.com/johnowhitaker/physarium">repo</a>.</p>
<section id="let-the-gradients-flow-bilinear-splatting-and-sampling" class="level2">
<h2 class="anchored" data-anchor-id="let-the-gradients-flow-bilinear-splatting-and-sampling">Let The Gradients Flow: Bilinear Splatting and Sampling</h2>
<p>When you have a particle with some x,y position, say, 7.2, 8.5, one way you could ‘draw’ it or have it deposit ‘pheremones’ to the fixed grid of the substrate is to round off and use the nearest pixel. BUT this throws away some signal - how are you supposed to get a gradient that would nudge the particle a little to the left? Instead, it’s generally a good idea to distribute the signal around the nearest pixels based on distance:</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://johnowhitaker.dev/mini-projects/images/slime_splat.png" class="img-fluid figure-img"></p>
<figcaption>Bilinear splatting in action</figcaption>
</figure>
</div>
<p>The same goes for sampling. Each particle reads some info from its neighborhood - by doing so with this bilinear sampling approach, we can get signals like “it would be better if that signal was stronger to the left” (hand waving here obviously).</p>
</section>
<section id="perception-communication-state" class="level2">
<h2 class="anchored" data-anchor-id="perception-communication-state">Perception, Communication, State</h2>
<p>We have a bunch of agents (say, 4096) and a shared grid (say, 96px square). Each agent has a position, velocity, a color it can deposit, and some internal state. They ‘perceive’ their neighborhood, then choose how to modify their directions and the pheremones + colors they are depositing.</p>
<p>Importantly, the agents can’t see eachother directly (this makes life a lot easier - training boids or something where each particle reacts to other nearby particles requires a LOT of tricks to make things performant). Instead, they can interact indirectly, by reading pheremones from the grid. I also ‘splat’ a density signal to the grid, which they can read to see how many other agents are/were nearby.</p>
<p>For perception, traditional slime sims use only two or three ‘antennae’ - to give mine a better chance of learning, I expanded to a 5x5 pixel grid that they can see. You can have this aliogned to the agent’s direction of movement, or “world aligned” (I tested both) - I kept it world aligned for ease of computation.</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://johnowhitaker.dev/mini-projects/images/slime_arch.png" class="img-fluid figure-img"></p>
<figcaption>A rough diagram of the overall architecture</figcaption>
</figure>
</div>
<p>I also gave them a few gradients, one radial, one vertical and one horizontal. This feels a little cheaty, but not implausibly so. Otherwise we could end up with rotationally invariant setups that would really struggle to produce a target image. The jax code gives a nice concise look at the key bits:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb1-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> State(NamedTuple):</span>
<span id="cb1-2">    field: jax.Array       <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># [H,W,communication]</span></span>
<span id="cb1-3">    canvas: jax.Array      <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># [H,W,RGB]</span></span>
<span id="cb1-4">    pos: jax.Array         <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># [agents,xy]</span></span>
<span id="cb1-5">    heading: jax.Array     <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># [agents,xy]</span></span>
<span id="cb1-6">    hidden: jax.Array      <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># [agents,memory]</span></span>
<span id="cb1-7">    pigment: jax.Array     <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># [agents,RGB]</span></span></code></pre></div></div>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb2-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> perceive(state):</span>
<span id="cb2-2">    density <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> density_field(state.pos, CFG[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"grid"</span>], CFG[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"grid"</span>])</span>
<span id="cb2-3">    expected <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> CFG[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"agents"</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> CFG[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"grid"</span>]<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">**</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span></span>
<span id="cb2-4">    density <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> jnp.log1p(density <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> (expected <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1e-6</span>))</span>
<span id="cb2-5">    world <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> jnp.concatenate([state.field, state.canvas, density], axis<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)</span>
<span id="cb2-6">    sample_pos <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> state.pos[:, <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>, :] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> OFFSETS[<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>, :, :]</span>
<span id="cb2-7">    local_patch <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> bilinear_sample(world, sample_pos.reshape(<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>))</span>
<span id="cb2-8">    local_patch <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> local_patch.reshape(CFG[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"agents"</span>], CFG[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"patch"</span>]<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">**</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)</span>
<span id="cb2-9">    cue_center <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> bilinear_sample(CUES, state.pos)</span>
<span id="cb2-10">    global_inputs <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> jnp.concatenate([state.heading, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> state.pigment <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, cue_center], axis<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)</span>
<span id="cb2-11">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> local_patch, global_inputs</span>
<span id="cb2-12"></span>
<span id="cb2-13"></span>
<span id="cb2-14"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> brain(params, local_patch, global_inputs, hidden):</span>
<span id="cb2-15">    spatial <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> jnp.tanh(jnp.einsum(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"nkc,ks-&gt;ncs"</span>, local_patch, params[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"spatial"</span>]))</span>
<span id="cb2-16">    encoded <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> jnp.concatenate([spatial.reshape(CFG[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"agents"</span>], <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>), global_inputs], axis<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)</span>
<span id="cb2-17">    encoded <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> jax.nn.silu(linear(params[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"encoder1"</span>], encoded))</span>
<span id="cb2-18">    encoded <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> jnp.tanh(linear(params[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"encoder2"</span>], encoded))</span>
<span id="cb2-19">    hidden <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> gru(params[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"gru"</span>], encoded, hidden)</span>
<span id="cb2-20">    trunk <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> layer_norm(hidden, params[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"ln_scale"</span>], params[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"ln_bias"</span>])</span>
<span id="cb2-21">    trunk <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> jax.nn.silu(linear(params[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"trunk1"</span>], trunk))</span>
<span id="cb2-22">    trunk <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> jax.nn.silu(linear(params[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"trunk2"</span>], trunk))</span>
<span id="cb2-23">    out <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> linear(params[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"head"</span>], trunk)</span>
<span id="cb2-24">    turn <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> CFG[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"max_turn"</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> jnp.tanh(out[:, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>])</span>
<span id="cb2-25">    speed <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> CFG[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"base_step"</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> jnp.exp(<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.35</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> jnp.tanh(out[:, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>]))</span>
<span id="cb2-26">    emission <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> jnp.tanh(out[:, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>:<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> CFG[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"comm"</span>]])</span>
<span id="cb2-27">    gate <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> jax.nn.sigmoid(out[:, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> CFG[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"comm"</span>]])</span>
<span id="cb2-28">    proposal <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> jax.nn.sigmoid(out[:, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> CFG[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"comm"</span>]:])</span>
<span id="cb2-29">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> turn, speed, emission, gate, proposal, hidden</span></code></pre></div></div>
</section>
<section id="learning-something-interesting" class="level2">
<h2 class="anchored" data-anchor-id="learning-something-interesting">Learning Something Interesting</h2>
<p>I tried re-creating some image textures using a texture loss as in the fantastic uNCA work (todo link). This works as long as the texture you want is blobs :D After poking at it for a while I was almost ready to call it on neural physarium, but then the day before my call with Ehsan to show (lack of) progress on our silly quest, I gave it one more go with codex, brain dumpimg all my ideas and challenging it to get some shapes growing. Success!</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://johnowhitaker.dev/mini-projects/images/shapes.png" class="img-fluid figure-img"></p>
<figcaption>Shapes made by particles in motion</figcaption>
</figure>
</div>
<p>Then, with further pushing, we were able to get persistent painting working too:</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://johnowhitaker.dev/mini-projects/images/slime_growth.png" class="img-fluid figure-img"></p>
<figcaption>Growing a mushroom</figcaption>
</figure>
</div>
<p>The agents deposit pigment, which gradually fades and diffuses, so they must learn to keep it topped up, too. It is even somewhat robust to peterbations too, since we train with some degradation. CHeck out <a href="https://x.com/johnowhitaker/status/2084144556682117146?s=20">this tweet</a> to see a video of me playing with an interactive toy I made to show this.</p>
<p>I use the pooled training as developed in the original NCA distil.pub article. The mushroom emoji is the best of the lot - thanks to the constant motion, finer details or more complex shapes can be a bit of a mess. Most of my exporiments looked more like this:</p>
<p><img src="https://johnowhitaker.dev/mini-projects/images/slime_emojis.png" class="img-fluid"></p>
<p>Still, better than nothing!</p>
</section>
<section id="an-evolving-way-of-doing-research" class="level2">
<h2 class="anchored" data-anchor-id="an-evolving-way-of-doing-research">An Evolving Way Of Doing Research</h2>
<p>I didn’t really write code for this project. Sure, I seeded it with some older code of mine, and described things in pretty fine detail as we went along. But Codex did the hard work - and this meant things got done that I would never have patience for. Lots of tweaking of the loss function to eke out extra gains. Easy ablations of things like rotating the perception vs keeping it world aligned. HTML research logs with lots of nice animations, interactive demos, and loss curves. Parallel training runs to test things using model for compute. I felt more like a thesis supervisor with an amazingly productive student, rather than a researcher in the trenches. And, as A.M. quipped when I said I’d made them a jax port: frameworks don’t really matter these days! Wild times.</p>
<p>Anyway, I’ll try to make a video on these too, but for now I hope this blog post at least serves as a minimal sort of write-up/reference to point at. This is a fun and unexpored area, I hope this inspires you to give it a poke too. Good luck, LMK if you train anything cool - J</p>


</section>

 ]]></description>
  <category>blogs</category>
  <guid>https://johnowhitaker.dev/mini-projects/physarium.html</guid>
  <pubDate>Tue, 25 Aug 2026 00:00:00 GMT</pubDate>
</item>
<item>
  <title>Quo Vadis?</title>
  <link>https://johnowhitaker.dev/essays/quo_vadis.html</link>
  <description><![CDATA[ 




<p>I took the summer off as a sabbatical from answer.ai, to travel with my wife, tinker with some side-projects, and spend some thinking through what I want to do in what feels like a particularly pivotal time. I’ve decided not to return to answer, but am still unsure about how to choose between my remaining options. This essay is for me, to consider them all in one place, and for everyone else who has been asking me what I’m going to do now :)</p>
<p>I drafted this with a bunch of bullets, and may leave them rather than expanding. You can ask your AI to complete it if you prefer fancy writing.</p>
<section id="why-now" class="level2">
<h2 class="anchored" data-anchor-id="why-now">Why Now?</h2>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://johnowhitaker.dev/essays/eci_plot.png" class="img-fluid figure-img"></p>
<figcaption>Measures like the Epoch Capabilities Index track the continued improvement of models (source: https://epoch.ai/eci)</figcaption>
</figure>
</div>
<ul>
<li>AI is getting v. good</li>
<li>Software skills may have a limited remaining shelf life</li>
<li>I want to step back and see if I’m in the best spot to contribute vs doing work that will happen anyway</li>
<li>It was good for me to have a break from ‘participation’, to sit back and relax and spectate rather than being plugged-in to each model release and AI news story. I think I was pretty burnt out, as <a href="https://www.interconnects.ai/p/burning-out">many in the space are</a></li>
</ul>
</section>
<section id="factors-to-consider" class="level2">
<h2 class="anchored" data-anchor-id="factors-to-consider">Factors To Consider</h2>
<ul>
<li>Do I enjoy it (e.g.&nbsp;I like teaching)</li>
<li>Does it have lasting value (e.g.&nbsp;teaching some specific tech &lt;&lt; teaching some general principle)</li>
<li>Does it have +ve impact (I want to do good)</li>
<li>Are other people doing it? (I prefer less populous areas of the map)</li>
<li>Does it pay? (I’ve historically skewed unprofitable-but-fun, now worry about financial footing given how many of the skills I have might tank in value as they get automated)</li>
</ul>
<p>With that, let’s look at a few options.</p>
</section>
<section id="back-to-the-mines-ai-research" class="level2">
<h2 class="anchored" data-anchor-id="back-to-the-mines-ai-research">1) Back To The Mines (AI Research)</h2>
<p>Lots of effort and capital is flowing into AI research right now. Many things feel ‘overdetermined’ - that is, they’re going to happen pretty much regardless of what I or any other individual chooses to work on. That said, even if you think AI research is on track to be fully automated in the next few years, you can’t look at the <strong>current</strong> state of things and not see that there is still LOTS of work to do:</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://johnowhitaker.dev/essays/jack_bugfinding.png" class="img-fluid figure-img"></p>
<figcaption>There is plenty of room for careful work still - most things are still piles of hacks with lots of space for improvement!</figcaption>
</figure>
</div>
<p>I’m a little rusty on model training, but I have &gt; a decade of experience at this point, and am sure I could find ways to contribute. There are organizations working to build AI that benefits people, doing hard + interesting technical work, that pay decently. I’m not sure the day-to-day of begging codex to fix distributed training code would be my <em>favourite</em> thing to do, especially if I don’t have a compelling reason to want the result NOW (vs a little later, without me having to lift a finger). Still, I’m chatting to a few people who may convince me that their org is doing cool enough stuff that I’m compelled to chip in and help it along :)</p>
</section>
<section id="frontier" class="level2">
<h2 class="anchored" data-anchor-id="frontier">2) Frontier?</h2>
<ul>
<li>Probably not, unless alignment or application like bio.</li>
</ul>
</section>
<section id="bio" class="level2">
<h2 class="anchored" data-anchor-id="bio">3) Bio?</h2>
<ul>
<li>Slow and expensive</li>
<li>Possibility (e.g.&nbsp;ML 2014) to bring it to many more people</li>
<li>…and build on models getting better, cheaper synthesis etc in the future</li>
<li>got some ideas…</li>
<li>but not quite yet. Can’t afford to pursue myself, but can keep on side (or beg for funding as a startup)</li>
</ul>
</section>
<section id="poke-the-world-with-science-course" class="level2">
<h2 class="anchored" data-anchor-id="poke-the-world-with-science-course">4) ‘Poke The World With Science’ course</h2>
<ul>
<li>Didn’t want to do a course</li>
<li>But came up with an idea I would be OK charging for, that I love</li>
<li>Participants get a kit of science stuff each [time period] and some lessons on a few things, then self-directed exploration using those principles to take stuff further and build up a tech tree for themselves over time</li>
<li>more on this soon possibly</li>
</ul>
</section>
<section id="shorter-term-projects" class="level2">
<h2 class="anchored" data-anchor-id="shorter-term-projects">Shorter-Term Projects</h2>
<ul>
<li>Hardware Hacking Evals (time sensitive, impactful, I can’t afford to try it on my own dime)</li>
<li>Portland DIYBio scene (exciting, I think we could build a commuinity here in time)</li>
<li>Electronics Kickstarter (might test the waters as a way back to EE)</li>
</ul>
</section>
<section id="why-not-answer.ai" class="level2">
<h2 class="anchored" data-anchor-id="why-not-answer.ai">Why Not Answer.AI</h2>
<p>I had a great time at Answer, they’re good people! Solveit was fun, although (personal opinion only) it feels like we made a fantastic tool for coding with AI in 2024, or learning to code with AI in 2025… As AI progress relentlessly continues, I feel the need to make some different bets. I’m sure they’ll continue to have fun teaching AI to code like Jeremy! And it’s good that there are people trying different things as we all explore what the heck programming looks like now and in the future :)</p>
</section>
<section id="you-tell-me" class="level2">
<h2 class="anchored" data-anchor-id="you-tell-me">You Tell Me!</h2>
<p>There are lots of areas I’m probably unaware of or underestimating! I’d love to hear from you if you have ideas for what I should work on! Even just my announcement on X that I’m formally unemployed has led to lots of lovely chats from people wanting to share the cool things they’re working on, which I am loving. I really appreciate everyoine who has taken time to talk to me and answer my unusual questions as I’ve been setting out to figure this stuff out.</p>
<p>Also, more directly than ideas, you (dear hypothetical reader) can steer what I do with the application of money. If there’s a direction you think I should go, a project you think I should do, and</p>
</section>
<section id="final-musings-faq" class="level2">
<h2 class="anchored" data-anchor-id="final-musings-faq">Final Musings &amp; FAQ</h2>
<p>One of the biggest sticking points when I talk through my decision space with people is my lack of motivation to work on things I think are going to get done. Someone is going to build it, why shouldn’t it be me? There are two main pieces where I think I am unusual as regards motivation: - The puzzle is important: the fun part for me is figuring out if something can be done, and how. Once I can see that I lose a lot of drive - and for many software problems now the ‘how’ is some variant of “wait for the AI to get good enough then ask nicely”. This takes a lot of the wind out of my sails. Obviously it is still worth doing the things, when you care about the outcome or will get paid to do it or whatever… but I care above average about having to figure out new stuff. - I am anti-trend. I really dislike working on things that lots of other people are working on for some reason. (This is a bad preference for career earnings!). I am so much happier if I can find something that wouldn’t otherwise happen, open up some new area of the map and then make it easier for others to come check it out and explore with me. This is why I loved AI art in 2020 and lost interest as it took off, why I’m drawn to niche things like neural cellular automata or DIY biology… IOW my explore vs exploit dial is set way over on ‘explore’.</p>
<p>Another frequent question is, given my penchant for teaching, why not teach courses? Once I find something cool worth teaching, I am allergic to monetizing it. I can’t stand charging for content - possibly because I grew up in a poor country where a price tag meant no access? I’ve also been very resistant to monetizing my YouTube/teaching content for fear of losing control over what I cover. If I have a paycheck attached to what I produce, that will heavily incentivize pushing towards the content in demand, chasing the hype. I might set up a Patreon this week and try to overcome these instincts to some extent, but this is why the list abive doesn’t feature things like running courses on agentmaxxing or whatever the current thing is.</p>
</section>
<section id="money-talk" class="level2">
<h2 class="anchored" data-anchor-id="money-talk">Money Talk</h2>
<p>People don’t like discussion salaries for some reason. Here are some figures to help you understand how I think about money:</p>
<ul>
<li>I grew up in Zimbabwe where many people live on less than $1 a day, so everything in tech sounds insane.</li>
<li>My first work was stats+ML+coding for low hundreds of dollars a month (for a good cause)</li>
<li>Out of college I was doing data science for low hundreds of dollars a day (for a good cause)</li>
<li>My salary at Answer.AI doing AI R&amp;D was something like $150,000 a year.</li>
<li>(This split e.g.&nbsp;last year to living: ~$50k, tax: ~40k giving: ~25k, IVF &amp; savings the rest - very approximate)</li>
<li>I’d consider short consulting for $500/hr if it’s a few hours, possibly less if it’s a longer time. Also, people who know me know that I can be expoited to work for free if you can nerd snipe me, feel free to exploit this information.</li>
<li>I’d been giving myself $500/month as a lab budget upper bound, that might have to drop while we’re drawing down savings. SO, if you want to contribute to me doing science stuff specifically, donating for lab spending specifically would make a huge difference to what I can do - this is also one reason I am thinking of starting a patreon even though it would be unlikely to move the needle much re: more general living expenses. Guilt-free budget for science is the dream :)</li>
</ul>
<p>Given the pace of improvement in AI coding, I am more inclined now than ever before to let money become a factor in what I do with my time. Right now, I am valuable for getting things to happen with computers, likely to be accelerated further as tools improve. But within a few years AI may well make my involvement less necessary - so those skills at least (i.e.&nbsp;software engineering specifically) feel like they have a couple of years left for me to capitalize on them.</p>


</section>

 ]]></description>
  <category>essays</category>
  <guid>https://johnowhitaker.dev/essays/quo_vadis.html</guid>
  <pubDate>Mon, 24 Aug 2026 00:00:00 GMT</pubDate>
</item>
<item>
  <title>Pipette Bot Goes To Teardown</title>
  <link>https://johnowhitaker.dev/misc/pipettebot.html</link>
  <description><![CDATA[ 




<p>Hello, teardown attendees :) You likely got here clicking the ‘How Does This Work?’ link from the pipette bot demo. This page has a little more about the bot, and a little more about the other things we may have spoken about. After the event it will hopefully have a gallery of some of the art created this weekend!</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://johnowhitaker.dev/misc/thumbnails/pipettebot.png" class="img-fluid figure-img"></p>
<figcaption>The bot with some demo artworks</figcaption>
</figure>
</div>
<section id="the-hardware" class="level2">
<h2 class="anchored" data-anchor-id="the-hardware">The Hardware</h2>
<p><img src="https://johnowhitaker.dev/posts/images/cnc_pipette.png" class="img-fluid"></p>
<p>The ‘motion platform’ is an Ender 3 V3 SE 3D printer that has served me well over the past few years. A Raspberry Pi hosts the site and sends G-CODE to the printer. The micropipette sits in a 3D printed holder with a Dynamixel XL430 servo and control board in charge of pushing the plunger. You can swap in a lower-volume micropipette for more precise work. The servo is one I happened to have on hand from a past robotics project, a little pricier than necessary for this application I suspect. Paper is held to the bed by magnets. <a href="https://johnowhitaker.dev/posts/cnc_pipette.html">original conception</a></p>
</section>
<section id="the-code" class="level2">
<h2 class="anchored" data-anchor-id="the-code">The Code</h2>
<p>To turn this into a demo I <del>waved a magic wand</del>~ asked Codex to whip up an interface based on the previous code I used to give the bot a control API. The resulting code is <a href="https://github.com/johnowhitaker/pipette/tree/codex/pipette-pixels">here</a> but comes with no warranty, not even the implied warranty of ‘a human has read this at some point’. My opinion on interfaces like these is that it’s most fun to roll your own to your exact specifications - the latest coding models speak web UI and g-code well enough to bash something together in minutes. This demo was completed from a voice prompt + a couple of follow up requests while I rode the bus to the Portland Art Museum on Wednesday :)</p>
</section>
<section id="operation" class="level2">
<h2 class="anchored" data-anchor-id="operation">Operation</h2>
<p>You draw a picture, which gets added to the queue. It is ‘painted’ by dispensing droplets of liquid in a grid pattern - by default they sort of sit there on the paper. You can have fun by 1) wetting the paper first using the spray bottle of water for some wet-on-wet watercolor vibes, 2) spraying afterwards 3) dabbing with paper towel / tissue to get rid of excess liquid or 4) whatever you like.</p>
<p>If I’m not there, odds are the printer is in semi-auto mode. Put a piece of paper in the marked spot, stick it down with magnets on the corners, then hit the central scroll knob/button thingee on the printer interface and it should start on whatever the next drawing in the queue is. Given the crowd, I expect some surprises and take no responsibility for what happens.</p>
</section>
<section id="other-things-we-might-have-spoken-about" class="level2">
<h2 class="anchored" data-anchor-id="other-things-we-might-have-spoken-about">Other Things We Might Have Spoken About</h2>
<p>The recent <a href="https://johnowhitaker.dev/mini-hw-projects/microfluidics_1.html">microfluidics tests</a></p>
<p><img src="https://johnowhitaker.dev/misc/thumbnails/purple_tomato.png" class="img-fluid"></p>
<p>The purple tomato seeds are from a bioengineered tomato from norfolk healthy produce, are not for sale but can be shared with your local community (a lovely approach): https://www.norfolkhealthyproduce.com/pages/faqs</p>
<p>The duckweed has been transformed using agrobacterium carrying this RUBY plasmid: https://atinygreencell.com/products/pcambia2300-rubygreen</p>
<p>The DIY gene gun <a href="https://ln.johnowhitaker.com/entry/51">works</a> but is waiting for full tests, <a href="https://johnowhitaker.dev/mini-hw-projects/asgg2.html">here</a> is an early WIP post from a few months ago.</p>
<p>The DVD hacking adventure has a writeup <a href="https://johnowhitaker.dev/posts/dvd_hack.html">here</a></p>
<p>The e. coli engineering project has a writeup + video <a href="https://johnowhitaker.dev/misc/chasing_green.html">here</a></p>
<p>Anything else is probably linked somewhere on https://johnowhitaker.dev/ or https://x.com/johnowhitaker. My gmail is also johnowhitaker if you want to get in touch that way.</p>


</section>

 ]]></description>
  <category>mini-hw-projects</category>
  <category>bio</category>
  <guid>https://johnowhitaker.dev/misc/pipettebot.html</guid>
  <pubDate>Thu, 23 Jul 2026 00:00:00 GMT</pubDate>
</item>
<item>
  <title>Phosphorescent Poop from Leslie Gulch</title>
  <link>https://johnowhitaker.dev/posts/glowing_poop.html</link>
  <description><![CDATA[ 




<p>I was out at night with my 365nm UV flashlight looking for fluorescent minerals and photographing glowing bugs. You know, regular stuff. My wife and I noticed something glowy that stayed glowing after the light swept away - this lingering glow (‘phosphorescence’) is way less common than regular fluorescence. Turns out it was poop! Specifically, the urate-rich white cap on a bird/reptile dropping. I carried it back to camp and then back to Portland for further analysis.</p>
<p>Video <a href="https://www.youtube.com/shorts/crbw4qYpvz8">here</a> shows the effect in action. <a href="https://x.com/johnowhitaker/status/2073395647051681977?s=20">X/Twitter thread</a></p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://johnowhitaker.dev/posts/images/poopglow.png" class="img-fluid figure-img"></p>
<figcaption>analyzing the fading glow from video frames</figcaption>
</figure>
</div>
<p>It was only the hardened outer shell of the white cap that showed the effect. Putting some in water instantly killed any persistent glow. Breathing humid air over a sample increased the rate at which the glow faded. A night exposed to humid Portland air completely killed the effect too, although heating the sample at ~45 degrees C for a few hours restored it, as did leaving it in a bag with a pile of CaCl2. Restored glow decayed with tau~=1s.</p>
<p>A final note: the CaCl2 used as dessicant also showed phosphorescence??? If I hit it with my bright UV torch in the dark then turn off the torch, the round CaCl2 pellets (I bought these for food science stuff) also glow faintly for a sec or two. I have not seen this documented anywhere. Neither can I find any lit on glowy poop! My hunch is that this is just a hard thing to notice in general life, so lots probably goes un-documented.</p>
<p>I somewhat hopefully tested chicken poop - but alas even after a brief attempt at drying I saw no afterglow. Perhaps baking some in the desert sun for a week might change that, or perhaps whatever diet the mystery desert bird was eating is required to get the right mix of purines + matrix to set up the long-lasting triplet states responsible for the glow…</p>
<p>Anyway, fun stuff :)</p>



 ]]></description>
  <category>misc</category>
  <category>bio</category>
  <category>Videos</category>
  <guid>https://johnowhitaker.dev/posts/glowing_poop.html</guid>
  <pubDate>Mon, 06 Jul 2026 00:00:00 GMT</pubDate>
</item>
<item>
  <title>Fluidics with an FDM Printer (WIP)</title>
  <link>https://johnowhitaker.dev/mini-hw-projects/microfluidics_1.html</link>
  <description><![CDATA[ 




<p>I’ve been messing with making microfluidics (or maybe just fluidics, since the smallest features are still hundreds of micrometers at present). I think I’ve got quite a neat technique that I haven’t seen elsewhere. I made a video a couple of days ago with my first few tests:</p>
<div class="quarto-video ratio ratio-16x9"><iframe data-external="1" src="https://www.youtube.com/embed/wlhhsgIuuVw" title="" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen=""></iframe></div>
<p>Since then, I’ve managed to get rough proof-of-concept bits working for droplet generation and sorting, my two main goals. But I’ve also spilled a lot of mineral oil and dye, and keep breaking things just as I get them working :) Still, I want to write down some rudimentary notes on my process since I’m leaving for an off-grid trip then going camping so it’ll be a few weeks before I can resume work on this.</p>
<p><img src="https://johnowhitaker.dev/mini-hw-projects/images/fluidics_1.png" class="img-fluid"></p>
<p>To make something useful with microfluidics, you need 1) a way to lay out precice channels and geometry for liquids to flow in and 2) ways to control the flow of liquid through those channels. Existing techniques include micro-machining tracks in polycarbonate, resin printing molds then casting them with silicon and adhering that to plasma-cleaned glass, etc. A few people have had the idea to print onto glass with an FDM printer but find that plastic often doesn’t stick very well.</p>
<p>My main trick is to print the outlines I want (using a custom script [1] rather than relying on an existing slicer to get full control of flow etc) and then to place a coverslip over the top and set the whole thing on a hotplate at 180C until the plastic softens, melts to the glass and makes a good seal. You can see this happen over the course of a few seconds - leave it too long and the channels get thinner and eventually close. Too short and you won’t get a perfect seal. But just right, and you end up with a much stronger bond and precise, watertight channels a few hundred um wide.</p>
<p><img src="https://johnowhitaker.dev/mini-hw-projects/images/fluidics_2.png" class="img-fluid"></p>
<p>I then glue on ports (luer to 1/16” barb fittings for now) using UV curing resin glue, attach 1/16” tubing, and push fluid through. I printed syring holders, and push the syringes either manually (not very precise) or with stepper motors. With the latter, driving a threaded bolt that pushes the syringe, each microstep of the stepper results in a small movement of the fluid in the channels. Another avenue I explored for possible sorting was using a peristaltic puml-like arrangement, with a bearing pressing in the tube. Placing a finger on this while watching through the microscope is fun - pushing one direction with barely any motion scoots the internal fluids around pretty precisely, I could quite easily shift the droplets (which are about 10 nanoliters, so TINY) around one at a time into different channels with a little practice.</p>
<p>The trick of course will be doing this under computer control and at high speed, or at least doing it with high reliability.</p>
<p>Notes:</p>
<ul>
<li>I used olive oil for the original tests but have since moved to light mineral oil with Tween 80 (polysorbate 80) as a surfactant for the oil phase. I think dialing in the surfactant concentration is going to help with making good stable droplets.</li>
<li>The water phase likes to stick to the glass by default, one of the reasons I was getting co-flow rather than droplets in some early tests. I ran rainX through and let it sit for a while - this is a hydrophobic glass-coating product sold for car windshields. This seemed to help a lot, I’ll try to get some better before+after to show in a future video on this topic</li>
<li>Any air bubbles/pockets act like ‘capacitors’, which can be good to avoid too much pressure but bad if you’re trying to do something sudden, since they can absorb a pressure spike and release it (relatively) slowly.</li>
<li>Fluid motion at these scales can be a little unintuitive! But fun to play with</li>
<li>UV resin and superglue don’t stick very strongly to PP fittings, especially with mineral oil getting everywhere too. This is nice for undoing mistakes and re-using bits, bad for fragility. I want to try other, better approaches.</li>
<li>I’ve been thinking of using a custom PCB in place of the bottom glass slide - this would let me place inlets wherever I like via holes in the PCB, and let me try using electrodes to do droplet sorting.</li>
<li>I should try shrinky-dink microfluidics inspired by <a href="https://www.youtube.com/watch?v=eNBg_1GPuH0">the Thought Emporium’s old video</a> on that subject.</li>
<li>Hunching over a bench working on these fiddly, tiny things was bad for my back haha</li>
</ul>
<p>Anyway, this post is marked as WIP since I want to come back and tidy up + document all this a lot better, but hopefully between the initial quick video and this post the core idea is findable and out there. LMK if you have ideas to try or questions about how I do this.</p>
<p>[1] For now, the code is dumped in <a href="https://gist.github.com/johnowhitaker/b0bc4bd3a51d6d047851c5db4c4525b1">this gist</a> with an example command - I’ll tidy up and share once I get this reliably doing something useful.</p>
<section id="july-update" class="level2">
<h2 class="anchored" data-anchor-id="july-update">JULY update:</h2>
<p>Tween 80 is the opposite of Span 80 (the surfactant I tried to order) - I assumed they were synonyms! 5% Span 80 in mineral oil and suddenly I could easily do water-in-oil droplet generation :) I have also successfully tested laying down plastic on PCBs rather than glass slides and bonding on the cover slip - this opens up the possibility of easy holes + electrodes, I’ll be ordering some boards to try these ideas shortly. Droplet pics here: https://ln.johnowhitaker.com/entry/61</p>


</section>

 ]]></description>
  <category>mini-hw-projects</category>
  <category>Video</category>
  <guid>https://johnowhitaker.dev/mini-hw-projects/microfluidics_1.html</guid>
  <pubDate>Fri, 26 Jun 2026 00:00:00 GMT</pubDate>
</item>
<item>
  <title>Inkwash &amp; Personal Software</title>
  <link>https://johnowhitaker.dev/posts/inkwash.html</link>
  <description><![CDATA[ 




<p>I made <a href="https://johnowhitaker.github.io/inkwash/">Inkwash</a> over the weekend. It is a watercolor-like sketching experience, where you draw with ‘ink’ then move it around with a brush, complete with fluid dynamics, water drying, ink color bleeding, etc. Check out <a href="https://x.com/johnowhitaker/status/2065118226879811737?s=20">a video of an early version in action</a>, the <a href="https://johnowhitaker.github.io/inkwash/about">interactive explanation of how it works</a> or just check out the <a href="https://github.com/johnowhitaker/inkwash">Github repo</a>.</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://github.com/johnowhitaker/inkwash/blob/main/images/inkwash_tests.png?raw=true" class="img-fluid figure-img"></p>
<figcaption>A few test sketches from the first few days of tinkering</figcaption>
</figure>
</div>
<p>It is tailored specifically to my exact sketching style, which in real life involves a G2 pen and a water brush, plus occasional watercolors. For the first time, I have a digital approximation of that which lets me capture what I see in the same way. It is, for me, a perfect, joyful piece of software.</p>
<p>And the way I made it is extraordinary - or would be, if the magical hadn’t become mundane: I asked the computer for it. With a handful of <a href="https://github.com/johnowhitaker/inkwash/blob/main/prompts.md">prompts</a>, Claude Fable 5 built the app up and refined it with me. And, when asked, it spat out a delightful interactive <a href="https://johnowhitaker.github.io/inkwash/about">explanation</a> of the mechanics. This is the kind of thing that used to require weeks of loving dedication &amp; skill to make. Now, the majority of my ‘development’ time was playing with the app and thinking about what else would be neat to include.</p>
<p>The weekend before, I had a similar experience making a music looping <a href="https://github.com/johnowhitaker/magenta-realtime/tree/main/examples/looper">app</a> with GPT 5.5. in codex, albeit with a bit more involvement on my part:</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://johnowhitaker.dev/posts/images/looper.png" class="img-fluid figure-img"></p>
<figcaption>An AI-powered music looping app</figcaption>
</figure>
</div>
<p>I don’t quite know what to write about these things. Fable, specifically, feels like another jump in terms of understanding intent and being able to do the hard work to make things happen with software. And (barring government interference) things are only going to get better from here.</p>
<p>I don’t feel the need to drop everything and monetise/market these apps. If I’m sharing them at all, it’s to say ‘hey look, something fun to play with’. I am writing this post because I guess I should link these cool things I made from my site? But mostly they’re for me - bits of software that I wanted, and now have. I still see lots of cynical takes along the lines of ‘if AI is good then why aren’t there loads of new hit apps in the app store / new SAAS companies killing it’. Honestly, for me, the feeling is that software might end up looking a lot more personal. I have my fun art app now - I expect you to make yours, if you want it! It doesn’t have to be perfect, the code doesn’t have to look just right. The models will keep getting better, the scope of what we can make will keep growing, and (I hope) there is going to be so much fun stuff made in the future. Hooray? ¯_(ツ)_/¯</p>



 ]]></description>
  <category>blogs</category>
  <category>essays</category>
  <guid>https://johnowhitaker.dev/posts/inkwash.html</guid>
  <pubDate>Tue, 16 Jun 2026 00:00:00 GMT</pubDate>
</item>
<item>
  <title>Hunting for Miracles in the ESM Protein Atlas</title>
  <link>https://johnowhitaker.dev/posts/prot_extract.html</link>
  <description><![CDATA[ 




<p>I has messing about exploring the newly-released <a href="https://biohub.ai/esm/protein/atlas">ESM Protein Atlas</a>, and since I’m working on a construct for miraculin expression I though I’d take a look for miraculin-like proteins using the similarity measures made available through the biohub team’s models + SAEs.</p>
<div class="quarto-video ratio ratio-16x9"><iframe data-external="1" src="https://www.youtube.com/embed/mwGZb8zw83I" title="" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen=""></iframe></div>
<p>I was able to use both the impressively helpful integrated agent and my own code w/ codex to find close matches. Turns out, a few edible plants had proteins with high similarity scores, so I set out to extract and taste the protein from some of these to see if I could detect any noticeable effects. The video summarizes the workflow. Alas, no taste changes I could notice, but I did get to taste some weird clover extract and misc proetin mush so, there’s that :)</p>
<p>Protocol:</p>
<ul>
<li>5g leaves, pulp or other plant material</li>
<li>Chop up in ~20ml water with NaCl added to ~0.6M (e.g.&nbsp;1.5g salt)</li>
<li>Filter, add 80ml cold ethanol and leave it to sit (protein will precipitate out)</li>
<li>Spin down the precipitate, add more eth to wash, spin down again (ditch supernatant)</li>
<li>Resuspend in water to taste</li>
</ul>
<p>I did a blind taste-test in one case where I though maybe there was an effect, but could tell no difference between the heated (and presumably denatured) version and the plain extract.</p>
<p>It is possible that</p>
<ul>
<li>The extraction process was too harsh</li>
<li>The proteins of interest were too low concentration to detect</li>
<li>I was using the wrong parts of the plant</li>
<li>(most likely) the proteins tagged as similar to miraculin share structural similarities but have a different role</li>
</ul>
<p>Anyway, fun excuse to mess about with protein models and do some crude salt extraction + ethanol precipitations.</p>



 ]]></description>
  <category>misc</category>
  <category>bio</category>
  <category>Video</category>
  <guid>https://johnowhitaker.dev/posts/prot_extract.html</guid>
  <pubDate>Tue, 02 Jun 2026 00:00:00 GMT</pubDate>
  <media:content url="https://johnowhitaker.dev/posts/images/prot_extract-Cover.jpg" medium="image" type="image/jpeg"/>
</item>
<item>
  <title>Messing about with ESMC</title>
  <link>https://johnowhitaker.dev/posts/generative_mutate.html</link>
  <description><![CDATA[ 




<p>Rambly video poking at some of the new models released by Biohub, seeing if leveraging their protein LLM can give us better ways to mutate candidate sequences for downstream tasks. I AM A NOOB - do not take anything in this video as gospel :) Corrections welcomed, comments and thoughts appreciated!</p>
<div class="quarto-video ratio ratio-16x9"><iframe data-external="1" src="https://www.youtube.com/embed/csLSuLVAzHo" title="" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen=""></iframe></div>
<p><a href="https://colab.research.google.com/drive/1mvqlTVIScs6mMVhw0ZlyXxnan6RuWjxg?usp=sharing">Notebook link</a></p>
<p>ESM gives a masked language model that operates on amino acid sequences, with each AA being a token. The main idea from the video is to take advantage of this when making a mutation of a sequence, by replacing random AAs with ones the model things are plausible/likely, ranther than completely randomly replacing them.</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://johnowhitaker.dev/posts/images/gen_mut1.png" class="img-fluid figure-img"></p>
<figcaption>Visualizing some mutation examples</figcaption>
</figure>
</div>
<p>I had AI help cobble together a quick and dirty test, comparing random mutations with this approach on a made-up task around a cohesin target + dockerin binder. The score function is a little fudged, although even ‘real’ prot engineering tasks do tend to blend in model prediction confidence as part of their score functions. Anyway, at least in this mini demo, the new mutation strategy produces higher-scoring candidates than random:</p>
<p><img src="https://johnowhitaker.dev/posts/images/gen_mut2.png" class="img-fluid"></p>
<p>After this initial dabble, I did check out the newly-released actual binder design flow that the biohub people shared, and it is a lot fancier and more involved. Still - at one point they do initialize the mutable AA positions with <code>0.01 * torch.randn(...)</code> and I thought, hey, maybe I could instead use the logits from the model, inspired by the dabbles above. This at first appeared to work really well - the total loss (all I looked at initially) was way lower! But this is just because the loss includes a term comparing the model prior with the learned dist, and by initializing based on the model prior we obviously do well on that! And, it turns out, worse on everything else that actually matters:</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://johnowhitaker.dev/posts/images/gen_mut3.png" class="img-fluid figure-img"></p>
<figcaption>Testing my init strategy on an antibody design task based on the biohub work.</figcaption>
</figure>
</div>
<p>Still, nice to see how the pros actually do this stuff. And it does feel like there might well be plenty of space for speed improvements, I may have to spend more of my modal credits trying to improve this more generally with all my old training/optimization tricks :)</p>



 ]]></description>
  <category>bio</category>
  <category>Video</category>
  <guid>https://johnowhitaker.dev/posts/generative_mutate.html</guid>
  <pubDate>Fri, 29 May 2026 00:00:00 GMT</pubDate>
</item>
<item>
  <title>Teaching Neural Cellular Automata To Play Games</title>
  <link>https://johnowhitaker.dev/misc/nca_games.html</link>
  <description><![CDATA[ 




<p>Some tiny experiments in pushing on what NCA can do, showing my own tiny experiments but also pointing at some resources and hopefully inspiring you to try some new ideas yourself :)</p>
<div class="quarto-video ratio ratio-16x9"><iframe data-external="1" src="https://www.youtube.com/embed/0w_cVJoD2uk" title="" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen=""></iframe></div>
<p>Markdown file with hopefully enough info for your coding agent of choice to get your own experiments going: https://gist.github.com/johnowhitaker/bf664eb56dfe50b05d5ef319a0364dae</p>
<p>Key references:</p>
<ul>
<li>https://wandb.ai/johnowhitaker/nca/reports/Fun-With-Neural-Cellular-Automata–VmlldzoyMDQ5Mjg0</li>
<li>https://arxiv.org/abs/2111.13545</li>
<li>https://distill.pub/2020/growing-ca/</li>
</ul>
<p>Also, I did some content for fast.ai on NCAs hidden deep in <a href="https://youtu.be/PdNHkTLU2oQ?si=K0w67-4-yIgkD5KO&amp;t=4479">this video</a> - worth a look if you want more polished content from me on this stuff :)</p>
<p>A few days after that first video I also trained denoising NCAs, which were able to repair flower images somewhat plausibly! Very fun to play with. Video:</p>
<div class="quarto-video ratio ratio-16x9"><iframe data-external="1" src="https://www.youtube.com/embed/sjAFcJBetuw" title="" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen=""></iframe></div>
<p>PPS: Fable with a minimal prompt and the md file did a fantastic little project + writeup along similar lines to the first video, including some good explorations of info propagation speed factors and other clever little bits. Check out its writeup <a href="https://johnowhitaker.github.io/fabNCA/">here</a> (code <a href="https://github.com/johnowhitaker/fabNCA">here</a>). Hopefully we can do useful stuff with this model in the future if it ever comes back with more sensible safeguards :)</p>



 ]]></description>
  <category>Video</category>
  <guid>https://johnowhitaker.dev/misc/nca_games.html</guid>
  <pubDate>Wed, 13 May 2026 00:00:00 GMT</pubDate>
  <media:content url="https://johnowhitaker.dev/misc/thumbnails/nca_game.png" medium="image" type="image/png" height="102" width="144"/>
</item>
<item>
  <title>RE of a DVD Drive</title>
  <link>https://johnowhitaker.dev/posts/dvd_hack.html</link>
  <description><![CDATA[ 




<p>Over the past week, I’ve been trying to take control of an external DVD drive, reverse-engineering its firmware and re-programming it to do various things. In this post, I’ll document the journey to date worming our way in and beginning to modify the firmware. This will hopefully be followed by a part 2 using our new-found powers to teach the hardware some tricks.</p>
<p><img src="https://johnowhitaker.dev/posts/images/drive_internals.png" class="img-fluid"></p>
<p>Why would anyone do this? My inspiration was the <a href="https://scanlime.org/tag/coastermelt/">coastermelt</a> project by Micah Elizabeth Scott (aka scanlime) 12 years ago, which attempted something similar and phrased the justification in a very compelling way: a DVD drive is a robot! One with multiple motors that can be driven with high precision, multiple lasers and focusing machinery, capable of etching sub-micrometer patterns onto plastic disks… It’s just that we typically only use them to read or write one very specific kind of pattern. If we could only control this amazing hardware, perhaps we could do a lot more!</p>
<p>There have been a few projects that reuse parts from CD/DVD drives to do cool things (<a href="https://kototoibashi.github.io/dvd-pickup-microscope-poc/">example</a>), like leveraging them in microscopy applications. But all of these tend to throw out the existing control circuitry and rebuild their own from scratch. This makes sense (it’s a lot easier), but it does mean that anyone wanting to duplicate the project must also build some extra hardware. Plus, it always felt weird to me - somewhere in here, there is already the right code and circuitry to make these things dance - it’s just that it was never made to be usable for anything besides its original purpose. Anyway, that’s what this project is all about - hacking it to use for our own purposes. Let’s walk through how we get there.</p>
<section id="first-contact" class="level2">
<h2 class="anchored" data-anchor-id="first-contact">First Contact:</h2>
<p>Inside the enclosure is a slimline SATA DVD drive module (model DS-8ABSH, which you can also buy <a href="https://www.amazon.com/Lite-DS-8ABSH-01-Internal-Burner-Writer/dp/B01N1XHE66">standalone on Amazon</a> for ~$30). It is connected to a USB-to-SATA adapter that has also been crammed into the enclosure, with a USB cable that you can then plug into your laptop. It shows up as a disk drive, and speaks SCSI - a standard way computers communicate with storage media of various kinds. It’s not uncommon for manufacturers to use extra, secret, undocumented SCSI commands as a sort of hidden communication channel for things like firmware updates - and so initial work included messing about seeing if there were any that produced interesting responses (and ejecting the drive or causing weird noises a lot in the process).</p>
<p><img src="https://johnowhitaker.dev/posts/images/drive_extainq.png" class="img-fluid"></p>
<p>At some point we dug up an old firmware update program, designed for DOS and Windows. Poking around in the binary, along with our scans, revealed a few special SCSI commands - including one which gave some Lite-On specific metadata, and one that let us dump a 1MiB firmware window. The firmware was encrypted, but the EXTRAINQ info gave the pieces needed to derive the AES key and IV and decrypt it, giving us our first look at (some of) the code running in the device.</p>
<p>The first chunk of that was 8051 assembly code, which I could load into Ghidra to decompile and feed to Codex to start exploring. We quickly spotted some promising-looking bits - now the question was, could we modify this and start running our own code instead?</p>
</section>
<section id="updaters-in-the-matrix" class="level2">
<h2 class="anchored" data-anchor-id="updaters-in-the-matrix">Updaters in The Matrix</h2>
<p>We needed to know the full sequence of steps involved in a firmware update, and after some guesswork based on static analysis of the DOS updater got us nowhere, we decided to run the program in a little simulated world where we could inspect exactly what it was sending during the process.</p>
<p>Pretty soon, we had the process mapped out well enough to replay an update with the firmware we’d dumped. But when we tried to make modifications, nothing happened. The process would run fine, we could even read the modified firmware back out - but nothing we did seemed to change much. In desperation, we even thought that the USB-SATA bridge might be filtering some key command, and I opened up an old MacBook from a friend to wire in the drive directly - no luck.</p>
<p><img src="https://johnowhitaker.dev/posts/images/drive_sata.png" class="img-fluid"></p>
<p>Alas, it wasn’t a missing ‘commit’ command after all: it turns out that there’s a firmware integrity step that happens before the drive allows new firmware to be written, and we were failing it. I even had a pretty good guess as to which set of bytes was the key checksum/stamp of approval, but it wasn’t anything simple that we could guess at. Here’s one place where having a tireless AI agent came in clutch. Codex searched relentlessly, finding a handful of updaters for different variants of this drive and others in the same family. Then it patched and re-patched our wine shim, adding more and more functionality as needed to convince these update utilities that they were programming drives, extracting out 5 or 6 ‘sibling’ firmware images (each with their own encoding nuances and tricks required) so that at the end we had a set of similar binary files we could compare.</p>
<p>Sure enough, those 14 bytes seemed like some sort of key/checksum, but no theory we could come up with revealed what, and despite a ton of work, we couldn’t find anything in any of the updater programs to help us out, nor could we see any code that checked it in the visible 8051 firmware. Still, at least these sibling images would come in handy as references later. For now, we faced a block - if we can’t modify the code, the project is sunk!</p>
</section>
<section id="papers-please-bypassing-firmware-integrity-checks" class="level2">
<h2 class="anchored" data-anchor-id="papers-please-bypassing-firmware-integrity-checks">Papers Please: Bypassing Firmware Integrity Checks</h2>
<p>We discovered that the official updater sends a separate profile-tail payload, which is essentially an 8051 helper overlay. By changing three bytes in this helper, we turn off a conditional and force the success path - sidestepping the firmware validity check entirely! So, now we can write whatever firmware we like…</p>
</section>
<section id="code-caves-and-trampolines" class="level2">
<h2 class="anchored" data-anchor-id="code-caves-and-trampolines">Code Caves and Trampolines</h2>
<p>The next step requires us to tread carefully. We’re planning to modify firmware that we don’t understand - a wrong step could leave the drive unresponsive and ‘bricked’. The standard approach here uses two delightfully named concepts. A code cave is a region that we can safely mutate - for example, we have a little 221-byte chunk of FFs at <code>0x6ee3</code> that can be safely over-written. We can write a piece of code there, and then patch some function somewhere to jump there (hence ‘trampoline’), execute our code, and then bounce back to where it was.</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://johnowhitaker.dev/posts/images/drive_code_cave.png" class="img-fluid figure-img"></p>
<figcaption>Artist’s depiction of this technique, lol</figcaption>
</figure>
</div>
<p>For example, a cautious first step added a delay function in the code cave and then triggered it during the update process while timing the different stages, giving a measurable timing difference. Next was reading out a single bit by conditionally setting an error state. Finally, we made something actually useful - patching the currentboot identity handler (<code>0x4fc9</code>) and using our code to modify the identity string. Now, we could issue a SCSI request and get back a customized response, which let us dump out data such as memory snapshots much faster than the one-bit-per-firmware-update early attempts.</p>
<p>Later on, this same idea was used to test all kinds of different hypotheses - we can do <em>anything</em> - as long as it fits in 221 bytes :)</p>
</section>
<section id="hardware-twiddling-and-mysterious-cdds" class="level2">
<h2 class="anchored" data-anchor-id="hardware-twiddling-and-mysterious-cdds">Hardware Twiddling and Mysterious CDDs</h2>
<p>We can see some 8051 code, but there’s also this big chunk that is encoded in some weird way. We tried a <em>ton</em> of different guesses, with no real luck. One thing stood out in the format/layout: a suspicious resemblance to some DVD encodings. The theory is that it uses some internal silicon decoder as part of the process.</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://johnowhitaker.dev/posts/images/drive_switch.png" class="img-fluid figure-img"></p>
<figcaption>Physical setup, with wires soldered to front panel connector and a way to physically power-cycle the drive</figcaption>
</figure>
</div>
<p>It took a while, but eventually we were able to map out some of the code path responsible for ‘decoding’ this into code/data at runtime, and build a helper to let us use the on-chip decoding path to slowly read out the materialized CDD contents one chunk at a time. Each run required power cycling the drive - fortunately, I’d long ago hooked up some circuitry to let Codex trigger a physical disconnect of the drive at will rather than relying on my meat fingers to do the boring labor. Unfortunately, those reads don’t see the full picture, only slices of the working window containing a few snippets of code. And our code runs in a different context to the normal execution, so couldn’t ever materialize the full, decoded block.</p>
<p>Still - the hardware cold reboot was useful for various parts of this. I later added another line that let me fake the switch that detects whether the sled is inserted, so that we could fake eject/insert operations with code as well. These hardware mods are useful for reverse engineering, but hopefully won’t be needed by anyone attempting to follow in my footsteps.</p>
</section>
<section id="the-state-of-things" class="level2">
<h2 class="anchored" data-anchor-id="the-state-of-things">The State Of Things</h2>
<p>TODO we still can’t fully decode CDDs. Can move sled around with standard SEEK(), and have hacks to spin drive, toggle laser and other party tricks but no proper full low-level control yet for things like focus / positioning.</p>
<p>TODO write better narrative arc, were we doomed, recovery, more details of various bits.</p>
<p>Actually, those TODOs capture it well. I have a few bricked drives, a few in various states of disassembly and functionality. And despite lots of head-banging, we don’t know how to fully decode or modify the controller code that would unlock further progress. I’m pausing this project for now.</p>
</section>
<section id="magic-ai-and-the-inevitable-pit-of-despair" class="level2">
<h2 class="anchored" data-anchor-id="magic-ai-and-the-inevitable-pit-of-despair">Magic AI and The Inevitable Pit of Despair</h2>
<p>~All of the code for this was written by Codex, with me steering and chiming in occasionally but otherwise mostly letting it go and making sure it had all the tools needed to explore ideas. This project specifically is one I’ve long had in mind as a sentinel of progress. Past models, even quite recently, made ~0 progress, so it was amazing to see codex (with some opus help) blast through the blockers one by one and make a ton of headway.</p>
<p>As with any AI project pushing up against the ceiling of what models are capable of, there came a time when progress began to slow and I found myself going in circles a little. It can be easy to get stuck here - “one more prompt will surely fix it” is a the natural feeling, one that I think causes lots of burnout and pain when people (myself included) miss the signs. I’ll be honest - this time around I noticed, but kept going past that point without snapping out of it - which is why I only have 2.5 drives out of four in working order!</p>
<p>When faced with this block, there are a few tricks you can try: - Wait for better models - Package up the core question and ‘ask the council’ - GPT 5.5 Pro, Opus 4.7, even Grok - you can ‘live in the future’ a little by leaning on the top tier models in aggregate. Sometimes this is just enough to break through some barrier - Actually do some work. A nice option if you do have the skills for it :) - Find a way around, approaching things from a different angle or working on some other aspect for a while.</p>
<p>In my case, a combination of fresh ideas from Pro, fresh motivation from me, and the addition of some DVDs to open up new code paths to explore broke the first deadlock and got us moving again, but now in the CDD decoding puzzle I sense a limit and will take a break rather than pushing in circles any further.</p>
</section>
<section id="conclusions" class="level2">
<h2 class="anchored" data-anchor-id="conclusions">Conclusions</h2>
<p>The original <a href="https://scanlime.org/tag/coastermelt/">coastermelt</a> project made a huge impression on me in high school - it’s a tour de force of RE, and introduced me to many ideas including the very concept of interactive disassembly, bit banging serial communications via a button/LED pins, the power of soldering on very tiny wires, etc etc. It inspired me to get into embedded engineering, and was one of the motivating factors behind my own RE journey - not that I ever reached anywhere close to scanlime’s level.</p>
<p>There’s a lot of hype around AI for cyber stuff at present, thanks to scary tales of Mythos’ exploits and high-profile vulnerabilities being reported at a scary rate. Seeing these models step through the RE workflows I’ve long associated with only a handful of experts was certainly eye-opening. We’re not at the “as good as an elite hardware hacker” level yet, but it suddenly feels in reach. And this heralds an exciting world where curious hackers can tinker with their devices, but also a scary one where less benign individuals could compromise the peripherals and embedded devices that surround us - and unlike your phone OS, it is unlikely that the good guys will be able to patch every old mouse, hard drive and wifi router sitting around in our houses!</p>
<p>Anyway, I hope you found this interesting. If you want to read more on how the code works, or try it yourself, I’ve dumped ~everything on github - start <a href="https://github.com/johnowhitaker/bmelt/blob/codex/clean-slate/minimal/README.md">here</a>. As mentioned, I’m hoping this is only the start and that at some point I’ll get proper control and make the hardware do some tricks… Hopefully see you in a future post soon :)</p>


</section>

 ]]></description>
  <category>misc</category>
  <guid>https://johnowhitaker.dev/posts/dvd_hack.html</guid>
  <pubDate>Tue, 05 May 2026 00:00:00 GMT</pubDate>
  <media:content url="https://johnowhitaker.dev/posts/images/drive_switch.png" medium="image" type="image/png" height="91" width="144"/>
</item>
<item>
  <title>Chasing Green - My Adventures in Genetic Engineering of Bacteria</title>
  <link>https://johnowhitaker.dev/misc/chasing_green.html</link>
  <description><![CDATA[ 




<section id="video" class="level3">
<h3 class="anchored" data-anchor-id="video">Video</h3>
<div class="quarto-video ratio ratio-16x9"><iframe data-external="1" src="https://www.youtube.com/embed/HwyRaDBEk2k" title="" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen=""></iframe></div>
</section>
<section id="additional-notes" class="level3">
<h3 class="anchored" data-anchor-id="additional-notes">Additional Notes</h3>
<p>I left this section as a TODO for a few months. At the time, I felt a bit let down that the project had ~ended without a clear win of an obvious visible green e.c. strain I could use for agar art. With the benefit of a few months break to give some perspective (I’m writing this now in July 2026) the project looks a lot nicer! I got to work through the stages of a genetic engineering workflow, re-writing little bits of the code of life! The end result isn’t perfect because, shocker, biology is complex… but still :) I’m very glad I got the chance to try this, and who knows, maybe at some point I’ll give it another crack.</p>
<p>References:</p>
<ul>
<li>My blog (has a few posts with more info on this under ‘bio’): https://johnowhitaker.dev/all.html</li>
<li>Frog paper: https://www.pnas.org/doi/10.1073/pnas…</li>
<li>Various plasmid designs: https://github.com/johnowhitaker/BLY</li>
<li>Extra paper on BBSs: “Structural and Functional Characterization of a Biliverdin-Binding Near-Infrared Fluorescent Protein From the Serpin Superfamily”</li>
</ul>
<p>Errata: I’d written “ALA supplementation” down and said Alanine multiple times in the video - brain fart! Not a heme precursor. That would be Aminolevulinate.</p>


</section>

 ]]></description>
  <category>Video</category>
  <category>bio</category>
  <guid>https://johnowhitaker.dev/misc/chasing_green.html</guid>
  <pubDate>Wed, 22 Apr 2026 00:00:00 GMT</pubDate>
  <media:content url="https://johnowhitaker.dev/misc/thumbnails/th.jpeg" medium="image" type="image/jpeg"/>
</item>
<item>
  <title>Misc Updatates</title>
  <link>https://johnowhitaker.dev/posts/misc_upd_apr2026.html</link>
  <description><![CDATA[ 




<p>I’ll start this post with various small updates of things I’ve been up to, then switch to reflection on why I’m taking an extended sabbatical, and how it’s going so far.</p>
<section id="small-things" class="level2">
<h2 class="anchored" data-anchor-id="small-things">Small Things</h2>
<p>Here’s a few recent experiments and such:</p>
<p><img src="https://johnowhitaker.dev/posts/images/tulipmap.png" class="img-fluid"></p>
<p>I made an <a href="https://johnowhitaker.github.io/tulipmap/">interactive site</a> to explore tulip varieties (<a href="https://github.com/johnowhitaker/tulipmap">code</a>, <a href="https://x.com/johnowhitaker/status/2042761964225757486?s=20">video demo</a>). The UMAP clusters of the CLIP embeddings map nicely to color and locale, made it very easy to get a general vibe for the genus - I’ll definitely be copying this for other taxa!</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://johnowhitaker.dev/posts/images/uv_fun.png" class="img-fluid figure-img"></p>
<figcaption>Some organisms under UV light</figcaption>
</figure>
</div>
<p>My wife and I camped out by the Oregon coast and I took some photos of things under my UV flashlight. More pics on my <a href="https://www.inaturalist.org/observations?user_id=jonathan_whitaker&amp;verifiable=any">iNaturalist</a> once I take em off my SD card.</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://johnowhitaker.dev/posts/images/tob_tlc.jpg" class="img-fluid figure-img"></p>
<figcaption>Initial spot far right, nic. smudge and solvent front middle-left</figcaption>
</figure>
</div>
<p>I extracted nicotine from tobacco and learned how to <a href="https://ln.johnowhitaker.com/entry/29">stain TLC plates with KMnO4</a>, accidental science art :)</p>
<p><img src="https://johnowhitaker.dev/posts/images/drink_crystals.png" class="img-fluid"></p>
<p>I tried an energy drink, crystalized some on a slide, <a href="https://ln.johnowhitaker.com/entry/30">used TLC to confirm that it does indeed have a ton of caffeine</a>, and logged my vitals over the day.</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://johnowhitaker.dev/posts/images/rainbow_ecoli.jpg" class="img-fluid figure-img"></p>
<figcaption>Rainbow e. coli</figcaption>
</figure>
</div>
<p>I noticed that some bacteria have <a href="https://ln.johnowhitaker.com/entry/28">a lovely rainbow effect in the right lighting</a></p>
<p><img src="https://johnowhitaker.dev/posts/images/decaf_tlc.jpg" class="img-fluid"></p>
<p>Tried <a href="https://ln.johnowhitaker.com/entry/31">taking some caffeine out of coffee with activated charcoal</a>, to some success! Image shows four references (1000, 500, 250 and 100 mg/L caffiene) and then some artificially strong coffee (left spot, rightmost plate) vs some that went through the charcoal decaf process (far right), with significantly less caffeine!</p>
<p>Others:</p>
<ul>
<li>Made a <a href="https://x.com/johnowhitaker/status/2044633992411205707?s=20">bad but fun theremin</a></li>
<li>Filmed some <a href="https://x.com/johnowhitaker/status/2042741123370901743?s=20">fun glowy fluid dynamics</a></li>
<li>Isolated a nice orange yeast https://ln.johnowhitaker.com/entry/27</li>
<li>Grew out third and final (?) attempt at green e. coli, https://ln.johnowhitaker.com/entry/32, cloning strain, ran out of amp so transformation into gave BL21 questionable results, satellite colonies, no strong color yet</li>
<li>Working on an explanation re: the green bacteria (<a href="https://x.com/johnowhitaker/status/2045501838519415032?s=20">WIP renders of the main proteins doing their thing</a>) - video coming soon</li>
<li>Agrobacterium transformation attempts on tomatoes, duckweed ongoing (another successful ish duckweed one worked for a couple of fronds, another attempt waiting for results)</li>
<li>Tending to various plants, since it’s the start of spring here! Growing out some purple tomatoes, some berries, etc.</li>
</ul>
</section>
<section id="sabbatical" class="level2">
<h2 class="anchored" data-anchor-id="sabbatical">Sabbatical!!!!</h2>
<p>Now the somewhat larger life update: I’m taking at least 6 months completely off work @ answer.ai. It feels like a particularly pivotal time in AI, and I want to reflect on what I should be working on going forward. Plus, I’d had an extended bout of sickness and was feeling a little burnt out and unproductive, so this will hopefully blow off the cobwebs.</p>
<p>So far, I’ve been really enjoying the wide open time (space) feeling. Not that work at answer was unpleasant - far from it - but ‘work’ is intertwined in my head with being productive, making progress; there are always outstanding TODOs. Having an empty agenda is a delightful contrast. As with all similar times in my life, my subconscious is frothing with a host if ideas that have been biding their time, possibilities that couldn’t quite justify themselves over the business. I’ve been trying to linger in this feeling, taking it easy, spending lots of down time walking and thinking and reading rather than rushing into the first ideas that pop up.</p>
<p>A less enjoyable note has been how quickly financial concerns popped up. We’ve got some runway of course, but now that my income has dropped to 0 I have to re-think a lab budget that was previously pegged at ‘a day’s income per month’ and figure out a balance between ‘not making the most of this time by trying to be too thrifty’ and ‘burning runway by spending too much’. I might have to factor in some notion of profitability into what I choose to do, something I’ve been fortunate at avoiding for most of my life. Such is the reality of living in a lovely but expensive place like Portland :) It’s not all downside though - I’ve already applied to one “micro-grant”, and making at least some work legible to such funding structures might impose at least a modicum of structure to an otherwise completely un-structured play time that could turn out helpful… But not yet, the focus for now is rest and recharge and fun.</p>
<p>As for directions I’m thinking of exploring, there are tugs in various directions:</p>
<ul>
<li>Everyone is running into AI applications, ‘agents’, etc. This strongly makes me want to run the other way - I much prefer exporing weird and ‘uncool’ spaces not the trendy <em>thing</em>.</li>
<li>But AI is going to be hugely impactful, and I might still be able to steer it in ways I’d like it to go</li>
<li>And I miss AI research. We’ve been very application-focused at answer, so I haven’t gotten to stretch my old deep learning + model training muscles in ages - there may be fun non-mainstream AI research work that could be fun, especially with coding agents there to skip some of the more painful parts of idea testing.</li>
<li>(Maybe even some mechanistic interpretability work? More niche, still space for fun new ideas, relatively impactful?)</li>
<li>On the other hand, I always love being in a field where I have a different set of skills to everyone else, so maybe I should get <em>further</em> from the AI core and instead help people without AI/coding/tech skills.</li>
<li>And DIY Bio is fun and intellectually stimulating, also humbling+frustrating in its ability to not go quite as planned. I might see if I can get a chance to work with some bio people in a lab nearby if I can weasel my way in</li>
<li>Making hardware used to be the thing I thought I’d end up doing. Now that I live in a country with McMaster Carr, Amazon Prime etc that might be a lot easier! Cons: does require some capital (as does bio) compared to messing with software, pros: holding real things in your hands and touching the world with your code is always cool.</li>
</ul>
<p>I might have been a little more burnt out than I like to admit, closely watching the ever-advancing frontier of AI and trying to keep up + work on things that at times felt temporary given impending advances… Rather than committing to any of the above soon, the short-term plan is to dabble with everything, slowly - spinning plates, looking for things that are fun rather than projecting out to impact or income. This usually works out well :)</p>
<p>Anyway, it’s an exciting experiment. I feel really good about maximizing flexibility at this time, and look forward to seeing how it evolves. If you have ideas or want to chat, well, my calendar is as open as it’ll ever be, feel free to reach out! And if you want to watch how this goes, keep an eye on this blog and on <a href="https://x.com/johnowhitaker">X</a>, I’ll try to keep logs of how this is going and what I’m up to. Until next time, J.</p>


</section>

 ]]></description>
  <category>misc</category>
  <guid>https://johnowhitaker.dev/posts/misc_upd_apr2026.html</guid>
  <pubDate>Sun, 19 Apr 2026 00:00:00 GMT</pubDate>
</item>
<item>
  <title>AGGG (Airsoft Gun Gene Gun) Part 2 - Hints of Success?</title>
  <link>https://johnowhitaker.dev/mini-hw-projects/asgg2.html</link>
  <description><![CDATA[ 




<p>In a <a href="https://johnowhitaker.dev/mini-hw-projects/airsoft-gene-gun.html">previous post</a>, I pointed out that a gene gun (fancy, $XXXXX) and a cheap airsoft gun share a lot in common. Now that I <a href="https://johnowhitaker.dev/posts/dnaq.html">have DNA</a>, it was time to pew pew - and it looks like maybe we got some transformations! I’ll explain what I’ve tried, what I found, what I’m thinking, and what I hope to try next.</p>
<p>Update (July 2026): More shots with both a RUBY reporter and a fluorescent eyGFPuv reporter show +ve transformation in carrot. <a href="https://ln.johnowhitaker.com/entry/51">Pics here</a>. I’m hoping to optimize the protocol and share a final post only once it’s working well.</p>
<p>Update (August 2026): Here is a <a href="https://www.youtube.com/watch?v=gRIMH3cx0iw">video</a> I recorded on this :)</p>
<section id="the-method" class="level2">
<h2 class="anchored" data-anchor-id="the-method">The Method</h2>
<p>I take a small amount (10uL) of a slurry of diatomaceous earth (DE), add some (10uL, 50mM) calcium chloride and some (~1ug, in ~2uL) plasmid DNA and place it on a piece of parafilm stretched over a 3D-printed tube. I dry it in a filtered stream of air, then the tube adapts to the end of my cheap airsoft pistol, and I fire it at a leaf from a few cm distance.</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://johnowhitaker.dev/mini-hw-projects/images/asgg_bullet_prep.jpg" class="img-fluid figure-img"></p>
<figcaption>‘Bullet’ prep with slurries of graphite powder and DE waiting to dry</figcaption>
</figure>
</div>
<p>The DNA I have codes for the ‘RUBY’ reporter, which expresses a red pigment. I extracted the DNA from bacteria carrying this plasmid which I got from <a href="https://atinygreencell.com/products/pcambia2300-rubygreen">Sebastian’s Biotech Bazaar</a>. I used <a href="https://bioland-sci.com/products/plasmid-miniprep-ii-kit-50-preps?pr_prod_strat=e5_desc&amp;pr_rec_id=821821b3f&amp;pr_rec_pid=8042713055421&amp;pr_ref_pid=8042712826045&amp;pr_seq=uniform">this miniprep kit</a> to purify out the plasmid DNA.</p>
</section>
<section id="findings" class="level2">
<h2 class="anchored" data-anchor-id="findings">Findings</h2>
<p>I shot some tobacco leaves, some duckweed, a slice of carrot and some onion… In the leaves, around the spots shot with DNA (but not the controls) there are some, well, reddish bits that show up after a few days! And after lots of squinting at them I’ve convinced myself that at least some of these are cells expressing RUBY :)</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://johnowhitaker.dev/mini-hw-projects/images/maybe_ruby.png" class="img-fluid figure-img"></p>
<figcaption>Possible RUBY expression - reddish cells/spots</figcaption>
</figure>
</div>
<p>My gallery is filled with blurry attempts to capture this, and none make it as obvious as I’d like. For example, there are also plenty of cases where, around an impact point where a clump of DE has hit, there’ll be some orange-ish crud welling up. Around most impacts (and all that I saw in the no-DNA controls) this stuff is pale white. Could this be RUBY too, expressed in some damaged cell(s) and being included in whatever is happening around the hole? Plus there are random red things just around the place - bits of fiber, random flakes - here’s a gallery of things I was more sceptical of:</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://johnowhitaker.dev/mini-hw-projects/images/maybe_not_ruby.png" class="img-fluid figure-img"></p>
<figcaption>Other things that might random fluff, browning, or other junk</figcaption>
</figure>
</div>
<p>The almost magenta dots in the carrot are apparently pretty much what <a href="(https://academic.oup.com/hr/article/10/4/uhad024/7036638)">RUBY looks like in carrot</a>, that was a YOLO shot with some leftover stuff, I’ll do some follow-on ones with carrot callus that I have growing and hopefully get a clearer signal. But in general it was much harder looking for red/orange reporter in orange carrot tissue!</p>
<p>I didn’t see anything in the onion I shot. Nor did I see anything in onion that I tried my micro-needle idea on. But then, RUBY doesn’t work everywhere - for example <a href="https://pmc.ncbi.nlm.nih.gov/articles/PMC12787607/">this paper documents it being a poor marker in coconut</a> (also a good ref for optimization strat once I can easily bombard and measure.)</p>
<p>For comparison: I’ve also been trying transformation with agrobacterium, which gives a much more macro result - here are some duckweed fronds expressing RUBY to various degrees:</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://johnowhitaker.dev/mini-hw-projects/images/dwe_tfm.jpg" class="img-fluid figure-img"></p>
<figcaption>RUBY expression at various levels in duckweed transformed with agrobacterium</figcaption>
</figure>
</div>
</section>
<section id="thoughts-what-didnt-work-what-next" class="level2">
<h2 class="anchored" data-anchor-id="thoughts-what-didnt-work-what-next">Thoughts, What didn’t work, What Next</h2>
<p>What didn’t work:</p>
<ul>
<li>Using graphite as the carrier (I wanted something more visible than the DE, so I sanded some graphite rod to get powder, but I didn’t ever see transformation using it)</li>
<li>Using my tungsten micro-needles seemed to kill leaf cells, and when I tried them more gently on onion I didn’t see any transformation</li>
<li>Bombarding duckweed was tricky (it tended to fly away in the blast) and I didn’t see any transformation of its tiny cells.</li>
</ul>
<p>I <em>think</em> the red spots are enough to indicate that we’ve gone from 0 to 1. From here, we can optimise - playing with</p>
<ul>
<li>DNA, carrier and buffer amounts</li>
<li>Nozzle diameter and geometry (I already switched to a converging-diverging nozzle which seems to give a much nicer shot). <a href="https://www.nature.com/articles/s41467-025-60761-x">Ref for flow guiding barrel design (just found)</a></li>
<li>Shot distance</li>
<li>etc.</li>
</ul>
<p>But I’m not happy with RUBY as a readout - too hard to see and measure. I’d love to use a flourescent reporter, but after months of trying I wasn’t able to get my hands on some. I have a construct with a fluorescent reporter ready to go for another project, which I’ll be ordering soon, for about $500… Once I get that I’m hoping we’ll be able to easily quantify how well a given shot does, and start optimizing everything. I wish individuals could order plasmids from addgene! Anyway.</p>
<p>Once you have some transformed cells, you can try to grow them out in tissue culture, optionally with something like herbicide resistance to select for the trasnformed ones. I’m hoping we can find a visual-selection-only flow since I don’t want to me making herbicide resistant plants or having other people work with them.</p>
<p>My dream is to work out a path for people who haven’t done this before, and don’t have the tools to do assembly and cloning, to still order a construct with their own protein/design, and get it into a plant, all as easily as possible. Wish me luck :)</p>


</section>

 ]]></description>
  <category>mini-hw-projects</category>
  <category>bio</category>
  <guid>https://johnowhitaker.dev/mini-hw-projects/asgg2.html</guid>
  <pubDate>Thu, 02 Apr 2026 00:00:00 GMT</pubDate>
  <media:content url="https://johnowhitaker.dev/mini-hw-projects/images/ruby_closeup.png" medium="image" type="image/png" height="139" width="144"/>
</item>
<item>
  <title>DNA quantification with a visible light spectrometer</title>
  <link>https://johnowhitaker.dev/posts/dnaq.html</link>
  <description><![CDATA[ 




<p>In microbio, a common procedure is a ‘miniprep’, where plasmid DNA is extracted from bacteria (often e. coli, which is good at making lots of copies) for insertion into some other organism. I’ve done a few, but had no good way to know if they worked! Typical approaches look at absorption at 260nm, and I don’t have a UV spectrometer. In this post I’ll show my trick for getting around this problem to estimate DNA concentration with my existing cobbled-together visible light spectrometer.</p>
<p>The key trick is to mix a small amount of the DNA sample with a dye that binds to DNA and fluoresces when illuminated with visible light. I used SeeGreen <a href="https://www.minipcr.com/product/seegreen-nucleic-acid-gel-stain/">stain</a> - I prepared a dilution with 2uL of stain in 20ml water, and then mix 1uL of DNA into 100uL of the diluted stain. A blue LED shines down into the sample. I use some yellow film to block most of this blue light from reaching the camera in the spectrometer, and instead look at the intensity of the green fluorescence emitted by the dye. The more DNA there is, the more dye binds and fluoresces, so this gives us a way to estimate DNA concentration.</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://johnowhitaker.dev/posts/images/dnaq1.png" class="img-fluid figure-img"></p>
<figcaption>Illuminating the sample with a blue LED</figcaption>
</figure>
</div>
<p>We also need a reference with known concentration. I used some spare plasmid I had on hand to make a 500ng/uL reference (well, I had to add 5uL of 100ng/uL DNA to the diluted stain, but close enough). And then as a baseline I made several vials to which no DNA was added. Here are the results, looking at a sample from my most recent miniprep and comparing to the standard and the two baseline samples:</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://johnowhitaker.dev/posts/images/dnaq2.png" class="img-fluid figure-img"></p>
<figcaption>Comparing spectra</figcaption>
</figure>
</div>
<p>Code is <a href="https://gist.github.com/johnowhitaker/3aa41553b027c29d307766fcc06a79cf">here</a> for the curious. Since the exact figures you’ll get vary depending on choices like the bounding box used in the image, I did some quick bootstrapping to give a range of values, picking a final estimate of 687 ng/µL (90% CI: 507–845).</p>
<p><img src="https://johnowhitaker.dev/posts/images/dnaq3.png" class="img-fluid"></p>
<p>Note, the confidence interval there only accounts for the variability of the image analysis. There could be additional variation from bad light sealing in the spectrometer, volume measurement errors, the fact that my reference sample was from a few uL of old DNA in a tube that had been shaken up, etc etc. I suspect the reference might be more like 300 ng/µL than 500, for example, which would knock the estimate down to around 400 ng/µL. Still, the miniprep kit targets 30-50ug total in 100uL elution, and I did try to stack the odds in my favour, so the estimate is in the right ballpark.</p>
<p>Importantly, the estimate is not 0, which is what I was really worried about :) If I wanted more precision, I’d do repeat estimates, average over multiple exposures, and make up more known reference samples. But for what I want, this is more than good enough to move on to the next steps of my project. Although I will want to do a gel to assess the quality of the DNA too - once I get my hands on a DNA ladder. At the moment we know there is DNA, but not how much is plasmid vs genomic junk that might have made it through :)</p>
<p>Anyway, that’s all, see you in the next one.</p>



 ]]></description>
  <category>bio</category>
  <category>mini-hw-projects</category>
  <guid>https://johnowhitaker.dev/posts/dnaq.html</guid>
  <pubDate>Thu, 19 Mar 2026 00:00:00 GMT</pubDate>
</item>
<item>
  <title>Spectral Shifts with Pyoverdine pH</title>
  <link>https://johnowhitaker.dev/mini-hw-projects/spectrometer.html</link>
  <description><![CDATA[ 




<p>Since my <a href="https://johnowhitaker.dev/posts/putida.html">previous post</a> I’ve continued tinkering with the flourescent pyoverdine produced by pseudomonas putida. I made some really pure stuff with solid phase extraction using activated charcoal (no luck getting access to LC-MS sadly) and also just extracted a bunch for further experiments. In todays post: what happens when you shift pH. Spoiler, fun color changes :) The first test:</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://johnowhitaker.dev/mini-hw-projects/images/pyoverdine_fl.png" class="img-fluid figure-img"></p>
<figcaption>Image lightly edited in an attempt to capture + emphasize what it looks like IRL</figcaption>
</figure>
</div>
<p>I’d noticed some older colonies shift to green, and green/yellow rather than blue when some stained my hands a while ago. So I started adding things to it to see what would happen. Citric acid and Vitamin C both shifted it to whitish, almost pink (at least in comparison to the normal color). Some strongly basic NaOH shifted it to green. To get a better picture of this, I made a spectrometer using a diffraction grating and a raspbery pi camera in a box. I calibrated it with a few laser pointers - by looking at the pixel values in one area of the image, we get a rough view of the spectrum (my slit is a fairly wide cut in some cardboard, nothing fancy here).</p>
<p><img src="https://johnowhitaker.dev/mini-hw-projects/images/spec_calib.png" class="img-fluid"></p>
<p>Putting some pyoverdine extract in ethanol in three vials, and adding some citric acid to one and NaOH to another, we get the following spectra:</p>
<p><img src="https://johnowhitaker.dev/mini-hw-projects/images/spec_p.png" class="img-fluid"></p>
<p>To me, this indicates that there might be two different flourescent compounds in the extract! More to investigate, I bought a tube to try my own LC to see if I can separate them.</p>
<p>A few more pics:</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://johnowhitaker.dev/mini-hw-projects/images/spec_cad.png" class="img-fluid figure-img"></p>
<figcaption>While cardboard and gaffer tape would work fine, I did print a nice enclosure and some holders to make this spectrometer useful for future work</figcaption>
</figure>
</div>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://johnowhitaker.dev/mini-hw-projects/images/pyoverdine_plates.png" class="img-fluid figure-img"></p>
<figcaption>One plate had e. coli growing before Pseudomonas putida took over</figcaption>
</figure>
</div>
<p>PS: <a href="https://gist.github.com/johnowhitaker/d9e8d41fa5c215dfd16b7c94f651ef3f">Code for the plots etc</a></p>



 ]]></description>
  <category>mini-hw-projects</category>
  <category>bio</category>
  <guid>https://johnowhitaker.dev/mini-hw-projects/spectrometer.html</guid>
  <pubDate>Fri, 27 Feb 2026 00:00:00 GMT</pubDate>
</item>
<item>
  <title>The Idea Is The Software</title>
  <link>https://johnowhitaker.dev/essays/distributables.html</link>
  <description><![CDATA[ 




<p>Here’s a device that only does X. Here’s a binary you can run that does X. Here’s code that you can compile and run that does X. Here’s an X SAAS. Here’s a prompt that creates code that does X. “Did you know computers can do X?”. [Half-baked musings on software incoming].</p>
<p>There’s always been a gap between ‘works on my machine’ and software that other people can easily use. So, if I coded up something that worked for me, it was a bit of a burden to polish that into something that others can use. You need to solve for different operating systems, you can’t have a hard-coded “~/johnos_projects” path, there are requirements… Plus there is some education you feel obligated to do, for potential users who might not know what <code>pip</code> is or something.</p>
<p>With LLMs getting good, new ideas around this are beginning to float about. What if we just shared, like, a really good prompt? Everyone could have their agent build their own bespoke version. Karpathy found a nice example of this being used as an extensibility mechanism for a project recently, where the repo includes prompts to tell LLMs how to add functionality:</p>
<blockquote class="blockquote">
<p>I also love their approach to configurability - it’s not done via config files it’s done via skills! For example, /add-telegram instructs your AI agent how to modify the actual code to integrate Telegram. I haven’t come across this yet and it slightly blew my mind earlier today as a new, AI-enabled approach to preventing config mess and if-then-else monsters. Basically - the implied new meta is to write the most maximally forkable repo and then have skills that fork it into any desired more exotic configuration.</p>
</blockquote>
<p>This weekend it really hit home to me how far this goes for some things: at the end of the day, all I need to share is that something is <strong>possible</strong> - the implementation is left as an exercise for the reader, but that is no longer a gatekeeping, elitist move! The specific example was my <a href="https://johnowhitaker.dev/mini-hw-projects/tachometer.html">quick and dirty strobe tachometer</a>. It’s a microcontroller, that lets you pulse an LED at different rates. When the flash speed matches the rotation speed of something, it looks like it’s standing still. Nothing fancy. Historically, if I wanted others to re-create this, I’d need to share:</p>
<ul>
<li>An exact parts list</li>
<li>A wiring diagram</li>
<li>The code</li>
<li>Installation instructions (how to install the Arduino IDE, how to connect your device)</li>
<li>Tips for connecting to the board (serial ports on Windows are a nightmare, for example)</li>
</ul>
<p>And odds are, a reader who wanted to re-create it won’t have the exact parts on hand. For e.g.&nbsp;the board I used is an ancient NodeMCU ESP8266 dev board from the hackerspace junk bin - but there is no reason to use that specifically.</p>
<p>Contrast to today. The thing I share is the <strong>idea</strong>: flash a light with a microcontroller to estimate speed. You can use a transistor to push more current through the LED if you want it bright. Ask your agent to use the arduino-cli to handle the programming etc.</p>
<p>I’m somewhat confident that many could replicate this project, and it won’t matter if they use an STM32 or a Raspberry Pi Pico or an ESP32. It won’t matter if they have the same display as me (I can’t even remember the model number, and did not tell codex - it figured it out anyway!). If they have different transistors on hand, I’m confident the model can tell them how to wire them up. In other words, the “source code” for this project is simply the notion that one can do such a thing - and all the tedious embedded engineering work is just an implementation detail that can be handled by an LLM.</p>
<p>In some ways this is the ‘LLMs are lossy compilers’ take that has been around for ages, but the point of this essay is me coming to terms with this now that the scope of tasks for which they are ~reliable is growing. There are lots of apps I’ve built for myself that would be a pain to release, since I’d need payments to cover inference costs, bla bla bla. It is somewhat freeing to think that maybe sharing a screenshot of the core idea is enough!</p>
<p>Of course, we’re still far from this working for all software. But it’s interesting to think how far you could push this, especially with OS building blocks. “Combine ThreeJS with [this mapping lib] and [that data API] to visualize cycle traffic in your city”. That’s a lot easier and more fun to type in and run than those old magazine code listings :) The future is going to be interesting!</p>
<p>PS: It’s worth thinking through how your medium of choice works for sharing these ‘idea seeds’. I’ve got some fun plans in solveit for this - a jupyter notebook packaging up demo code + suggested prompts seems like a nice format for this. Still trying to convince myself this is needed vs a simple .md file or screenshot, but we’ll see :)</p>
<p>PPS: What’s the coolest piece of software you’ve seen that can be distributed in this way? (i.e.&nbsp;as a one-shot prompt). I’d love to see cool examples.</p>



 ]]></description>
  <category>essays</category>
  <guid>https://johnowhitaker.dev/essays/distributables.html</guid>
  <pubDate>Mon, 23 Feb 2026 00:00:00 GMT</pubDate>
</item>
<item>
  <title>Quick and dirty strobe tachometer</title>
  <link>https://johnowhitaker.dev/mini-hw-projects/tachometer.html</link>
  <description><![CDATA[ 




<p>I wanted to know how fast my dremel ‘centrifuge’ spins, so I hooked up a transistor + LED to an ESP8266 module I had lying around, and had codex spin up something that gives me a webUI to vary the pulse rate of the LED. With a bit of fiddling you can figure out how fast something is spinning. Pics:</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://johnowhitaker.dev/mini-hw-projects/images/tach_3.jpg" class="img-fluid figure-img"></p>
<figcaption>The worst-looking circuit I’ve soldered in a while</figcaption>
</figure>
</div>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://johnowhitaker.dev/mini-hw-projects/images/tach_1.jpg" class="img-fluid figure-img"></p>
<figcaption>The Web UI that I can use to control the pulse rate from my phone</figcaption>
</figure>
</div>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://johnowhitaker.dev/mini-hw-projects/images/tach_2.jpg" class="img-fluid figure-img"></p>
<figcaption>I drew a dot on the spinny bit - symmetry can mean wrong estimates of speed</figcaption>
</figure>
</div>
<p>Doesn’t seem worth sharing the code, ideas if you want to re-create this:</p>
<ul>
<li>A transistor lets you push more current through an LED compared to wiring one directly to a pin of the ESP8266. I used a BC547 that I had on hand. The base of the transistor is connected to a GPIO pin of the ESP8266 through a resistor (e.g., 1kΩ), the emitter is connected to ground, and the collector is connected to the negative leg of the LED. The positive leg of the LED is connected to a power source (e.g., 3.3V or 5V) through a current-limiting resistor (e.g., 220Ω).</li>
<li>Ask for codex to close the loop - I didn’t want to be copying code back and forth to the Arduino IDE for this, I’m sick and I know just what I want. So I installed the arduino cli, and it was more than happy to use that plus misc scripts and such that it wrote for itself to find the board, connect, verify that it could read the serial output, etc etc. I basically just had to give it my WIFI creds and some gentle steering towards the behaviour I wanted.</li>
</ul>



 ]]></description>
  <category>mini-hw-projects</category>
  <guid>https://johnowhitaker.dev/mini-hw-projects/tachometer.html</guid>
  <pubDate>Thu, 19 Feb 2026 00:00:00 GMT</pubDate>
</item>
</channel>
</rss>
