<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
    <title>C. Renaudo — Dev Log</title>
    <subtitle>Particle and droplet science, Rust and Python engineering, AI-assisted development, and teaching.</subtitle>
    <link rel="self" type="application/atom+xml" href="https://carenaudo.github.io/carenaudo/atom.xml"/>
    <link rel="alternate" type="text/html" href="https://carenaudo.github.io/carenaudo"/>
    <generator uri="https://www.getzola.org/">Zola</generator>
    <updated>2026-09-03T00:00:00+00:00</updated>
    <id>https://carenaudo.github.io/carenaudo/atom.xml</id>
    <entry xml:lang="en">
        <title>Three things on a phone</title>
        <published>2026-09-03T00:00:00+00:00</published>
        <updated>2026-09-03T00:00:00+00:00</updated>
        
        <author>
          <name>C. Renaudo</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://carenaudo.github.io/carenaudo/blog/three-things-on-a-phone/"/>
        <id>https://carenaudo.github.io/carenaudo/blog/three-things-on-a-phone/</id>
        
        <content type="html" xml:base="https://carenaudo.github.io/carenaudo/blog/three-things-on-a-phone/">&lt;p&gt;Three Android apps this month, all Kotlin and Compose, none of them public. They have less in
common than the count suggests.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;A personal finance tracker.&lt;/strong&gt; I tried a number of the existing ones first, and none of them
worked the way I wanted — not for lack of features, but over where the data lives. I wanted
it entirely on the device, with nothing sent anywhere, and a plain export to CSV or XLSX any
time I want it back out. A private ledger I look at, not an account that syncs. That is a
preference, not a gap in the market, and it was easier to build than to argue with.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;A droplet evaporation simulator.&lt;/strong&gt; This one began as a question about the phone rather than
about the physics: could I get an Abramzon–Sirignano integration running properly on Android
— an adaptive Dormand–Prince step with a root-find nested inside each one? By desktop
standards that is not heavy computation, but it is well past what a spreadsheet does, and I
wanted to know how that class of problem behaves on a handset.&lt;/p&gt;
&lt;p&gt;It runs, and it runs fast — on the one phone I have actually tried it on, a Moto Edge 40 Pro.
Whether the numbers are &lt;em&gt;right&lt;/em&gt; is a separate question, and validating them on the device is
the part I have not solved. Speed was never going to be the hard half.&lt;/p&gt;
&lt;p&gt;The other half of it is reach. Type in a diameter, an air temperature and a humidity, get a
curve, without installing a Python environment first. Plenty of tools can integrate this
model; what I did not have was one in my pocket, and neither does a student with a question
and a phone.&lt;/p&gt;
&lt;p&gt;The physics lives in its own package with no Android dependencies at all, so the model can be
exercised without a phone anywhere near it. That separation was the first thing I did, the
thing I would defend hardest, and the route by which the numbers eventually get checked.
There are no tests yet; that is the next job, and until there are, treat what it prints as a
demonstration rather than a measurement.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The third one I am not going to describe.&lt;/strong&gt; It is an idea I am prototyping toward something
that might become a small venture, and writing about it now would be writing about an
intention rather than a result. When there is something to show, it will be here.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;Two of these turned out to be asking different questions. One was about where data sits and
who else gets to see it. The other was about whether the device in your hand can carry the
computation at all — which, this time, it could.&lt;/p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Reimplementing the LCF format in Rust</title>
        <published>2026-09-02T14:00:00+00:00</published>
        <updated>2026-09-02T14:00:00+00:00</updated>
        
        <author>
          <name>C. Renaudo</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://carenaudo.github.io/carenaudo/blog/lcf-core-notes/"/>
        <id>https://carenaudo.github.io/carenaudo/blog/lcf-core-notes/</id>
        
        <content type="html" xml:base="https://carenaudo.github.io/carenaudo/blog/lcf-core-notes/">&lt;p&gt;This one started with a build failure, not an ambition.&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https://github.com/carenaudo/easy-rpg-REditor&quot;&gt;easy-rpg-REditor&lt;/a&gt; needed to read RPG Maker
2000/2003 project files, and the reference implementation for that format is
&lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https://github.com/EasyRPG/liblcf&quot;&gt;&lt;code&gt;liblcf&lt;/code&gt;&lt;/a&gt; — a mature C++ library maintained by the
EasyRPG project. The obvious move is to bind to it. I spent a while fighting vcpkg and ICU
trying to get it linking statically under MSVC, and at some point the question stopped being
“how do I get this to build” and became “what would it take not to need it.”&lt;/p&gt;
&lt;p&gt;That is the second reason from &lt;a href=&quot;https://carenaudo.github.io/carenaudo/blog/a-log-for-the-tooling/&quot;&gt;the previous post&lt;/a&gt;:
the tool exists and is good, but the shape I needed it in — no C++ toolchain, no vcpkg, no
static-linking ritual for anyone who clones the repo — was specific enough that removing the
dependency was cheaper than carrying it.&lt;/p&gt;
&lt;h2 id=&quot;what-lcf-actually-is&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#what-lcf-actually-is&quot; aria-label=&quot;Anchor link for: what-lcf-actually-is&quot;&gt;What LCF actually is&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Four binary files, all the same format underneath:&lt;/p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;File&lt;/th&gt;&lt;th&gt;Holds&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;.ldb&lt;/code&gt;&lt;/td&gt;&lt;td&gt;The database — actors, items, skills, enemies, troops, chipsets, terms&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;.lmt&lt;/code&gt;&lt;/td&gt;&lt;td&gt;The map tree&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;.lmu&lt;/code&gt;&lt;/td&gt;&lt;td&gt;A single map&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;.lsd&lt;/code&gt;&lt;/td&gt;&lt;td&gt;A save file&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;The encoding is pleasantly simple. Every field on disk is:&lt;/p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color-scheme: light dark; color: light-dark(#24292E, #E1E4E8); background-color: light-dark(#FFFFFF, #24292E);&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;[chunk_id: varint][length: varint][payload]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The varint is the usual 7-bits-per-byte scheme with the top bit as a continuation flag. A
struct’s reader is a loop over chunks: if the id maps to a field it knows, decode the payload
by that field’s declared type; if it does not, seek forward by &lt;code&gt;length&lt;/code&gt; and carry on.&lt;/p&gt;
&lt;p&gt;That last clause is the single most useful property of the format. &lt;strong&gt;Unknown chunks are
skippable&lt;/strong&gt;, which means a reader that only understands a third of the fields still reads the
other two thirds correctly rather than desynchronising and producing garbage. It is what
makes an incremental port possible at all: you can hand-write three structs, test them
against real files, and know that the fields you have not gotten to yet are being stepped
over cleanly rather than silently corrupting their neighbours.&lt;/p&gt;
&lt;h2 id=&quot;the-easy-part-and-the-hard-part&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#the-easy-part-and-the-hard-part&quot; aria-label=&quot;Anchor link for: the-easy-part-and-the-hard-part&quot;&gt;The easy part and the hard part&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Reading is the easy part. The varint decoder and chunk loop are perhaps an afternoon, and
the writer’s varint encoder is a trivial mirror of the reader’s.&lt;/p&gt;
&lt;p&gt;The hard part is deciding &lt;em&gt;what to write at all&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;The rule LCF uses is that a field’s chunk is emitted only if either the field is explicitly
marked as persist-if-default, or its current value differs from its declared default. Fields
sitting at their default are simply omitted from the file. This is what keeps output
byte-compatible with what RPG Maker’s own editor produces — and it means every single field
needs to know its own default before you can write it back.&lt;/p&gt;
&lt;p&gt;In liblcf’s schema, 348 of 1,044 fields carry the persist-if-default flag. The rest are
governed by a default comparison. Three things make that worse than it sounds:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Defaults are not always constants.&lt;/strong&gt; Some are version-conditional. &lt;code&gt;Actor.final_level&lt;/code&gt;
has a default of &lt;code&gt;50|99&lt;/code&gt; — 50 under RPG Maker 2000, 99 under 2003 — resolved against the
engine version recorded in the file itself. A port that hard-codes either number produces
files that are subtly wrong on the other engine.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Strings have to survive the round trip.&lt;/strong&gt; Text is stored in the project’s own 8-bit
encoding — Shift-JIS for a Japanese project, Windows-1252 for a Western one — so the write
path has to &lt;em&gt;encode&lt;/em&gt; back into it, not just decode out of it. &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https://docs.rs/encoding_rs&quot;&gt;&lt;code&gt;encoding_rs&lt;/code&gt;&lt;/a&gt;
handles this well: it is bidirectional, it is what Firefox uses, and it needs no C
toolchain, which was the whole point.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Array order and index semantics have to be exact.&lt;/strong&gt; This is the one that actually
frightens me. A read bug shows wrong data in the editor — visible, annoying, harmless. An
off-by-one in an array index produces a file that RPG Maker loads &lt;em&gt;happily&lt;/em&gt;, with the
wrong event sitting on the wrong map slot. The failure is silent and it is in the user’s
project, not in my program.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;why-the-schema-had-to-generate-the-code&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#why-the-schema-had-to-generate-the-code&quot; aria-label=&quot;Anchor link for: why-the-schema-had-to-generate-the-code&quot;&gt;Why the schema had to generate the code&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Seventy structs. Roughly 1,200 fields. Seventy-three enums.&lt;/p&gt;
&lt;p&gt;Hand-transcribing default values, persistence flags and version conditions across 1,200
fields is not engineering, it is dictation — and the error rate on dictation is not zero. It
is also exactly the work that liblcf’s own &lt;code&gt;generator/csv/*.csv&lt;/code&gt; schema already exists to
make mechanical. That schema is the same source of truth liblcf’s C++ is generated from, and
it has been validated against real RPG Maker output for years.&lt;/p&gt;
&lt;p&gt;So &lt;code&gt;lcf-core&lt;/code&gt; is generated from those CSVs by &lt;code&gt;lcf-codegen&lt;/code&gt;, a standalone Rust crate in the
same repository. Not liblcf’s Python and Jinja2 pipeline — reimplementing the consumer in
Rust keeps the build self-contained, so a contributor needs &lt;code&gt;cargo&lt;/code&gt; and nothing else.&lt;/p&gt;
&lt;p&gt;The payoff is where correctness is bounded. Consuming the schema means the question is “did
I translate the schema correctly,” which is one problem, rather than “did I transcribe 1,200
fields correctly,” which is 1,200 problems.&lt;/p&gt;
&lt;h2 id=&quot;the-sequencing-that-mattered&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#the-sequencing-that-mattered&quot; aria-label=&quot;Anchor link for: the-sequencing-that-mattered&quot;&gt;The sequencing that mattered&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The tempting move is to build the code generator first, since it is obviously the interesting
part.&lt;/p&gt;
&lt;p&gt;That is a trap, and it is worth naming: &lt;strong&gt;codegen built on unvalidated primitives compounds
one bug across all seventy structs simultaneously, instead of catching it on one.&lt;/strong&gt; A varint
off-by-one or a wrong default-comparison rule, discovered after generation, is seventy
identical bugs and a regenerate-and-pray cycle.&lt;/p&gt;
&lt;p&gt;So the order was: hand-write the reader and writer primitives and two or three real structs,
get round-trip byte-identity tests passing on actual game files, and only then point a
generator at the pattern those structs proved.&lt;/p&gt;
&lt;h2 id=&quot;round-tripping-is-the-only-test-that-counts&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#round-tripping-is-the-only-test-that-counts&quot; aria-label=&quot;Anchor link for: round-tripping-is-the-only-test-that-counts&quot;&gt;Round-tripping is the only test that counts&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The test is unglamorous and it is the whole safety net:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Read a real &lt;code&gt;.ldb&lt;/code&gt; / &lt;code&gt;.lmt&lt;/code&gt; / &lt;code&gt;.lmu&lt;/code&gt; / &lt;code&gt;.lsd&lt;/code&gt; with the Rust reader.&lt;/li&gt;
&lt;li&gt;Write it straight back out, unmodified.&lt;/li&gt;
&lt;li&gt;Byte-diff the output against the original.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Any difference has to be &lt;em&gt;explainable&lt;/em&gt;, not merely small. Where it currently stands:&lt;/p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color-scheme: light dark; color: light-dark(#24292E, #E1E4E8); background-color: light-dark(#FFFFFF, #24292E);&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;test test_ldb_roundtrip_2000 ... ok    (8 actors, 14 chipsets, 132 skills,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;                                        86 items, 81 enemies, 86 troops)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;test test_lmt_roundtrip_2000 ... ok    (81 maps)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;test test_lmt_roundtrip_2003 ... ok    (22 maps)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;test test_lmu_roundtrip_all_maps_2000 ... ok  (80 maps)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;test test_lmu_roundtrip_all_maps_2003 ... ok  (20 maps)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;test test_lsd_save_roundtrip ... ok&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Which is a real result and a narrow one. It says the format machinery survives contact with
the projects I have. It does not say it survives the enormous variety of real RPG Maker games
that exist, and I would not claim otherwise.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;lcf-core&lt;/code&gt; is alpha. That is not modesty, it is the status.&lt;/p&gt;
&lt;h2 id=&quot;the-false-claim-in-my-own-repository&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#the-false-claim-in-my-own-repository&quot; aria-label=&quot;Anchor link for: the-false-claim-in-my-own-repository&quot;&gt;The false claim in my own repository&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Which brings me to the part of this worth writing down.&lt;/p&gt;
&lt;p&gt;There is a document in this repository that opens by announcing complete &lt;strong&gt;100% API-level
feature parity&lt;/strong&gt; with C++ &lt;code&gt;liblcf&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;That claim is false. Nobody enumerated liblcf’s API and checked it off. What actually
happened is that eight round-trip tests passed, and somewhere between that result and the
document, it became a completeness claim about the entire library.&lt;/p&gt;
&lt;p&gt;I did not write that sentence, and that is exactly the point. It was produced in the middle
of an AI-assisted session, in the confident register these tools default to, and then it sat
in my repository — in my voice, under my name — looking like a fact I had established.
Months later it is indistinguishable from something I verified, because nothing about it
looks uncertain.&lt;/p&gt;
&lt;p&gt;This is the failure mode I would flag to anyone working this way. The assistant does not
hedge unless you make it. It will summarise “the tests I was told to run passed” as “100%
parity achieved,” because that is the shape of the sentence that usually follows in the
text it learned from. The generated code was checked by a test suite. The generated &lt;em&gt;prose
about&lt;/em&gt; the code was checked by nobody.&lt;/p&gt;
&lt;p&gt;Documentation written this way inherits the cadence of a finished result without inheriting
the work that would justify it — and unlike a code bug, no test ever fails to tell you.&lt;/p&gt;
&lt;h2 id=&quot;what-the-assistant-was-and-was-not-good-for&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#what-the-assistant-was-and-was-not-good-for&quot; aria-label=&quot;Anchor link for: what-the-assistant-was-and-was-not-good-for&quot;&gt;What the assistant was and was not good for&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;With that caveat sitting in front, the split was sharper here than on most work.&lt;/p&gt;
&lt;p&gt;It was genuinely good at the mechanical surface: 153 fields of terms and vocabulary, seventy
struct definitions, the enum translations. Wide, repetitive, well-specified work where the
schema says exactly what the answer is.&lt;/p&gt;
&lt;p&gt;It was not something I would trust unverified anywhere near the write path, and I did not.
The round-trip byte-diff is not a formality there; it is the thing standing between a
plausible-looking implementation and a corrupted project file. Plausible is precisely what a
language model is good at producing, and plausible is not the bar when the failure mode is
damaging someone’s game.&lt;/p&gt;
&lt;p&gt;So: fastest exactly where a schema or a test can check its work, most dangerous exactly where
neither can — and prose is the place where neither can.&lt;/p&gt;
&lt;h2 id=&quot;what-this-is-not&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#what-this-is-not&quot; aria-label=&quot;Anchor link for: what-this-is-not&quot;&gt;What this is not&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;It is not finished, and it is not a replacement for anything. &lt;code&gt;lcf-core&lt;/code&gt; is alpha: round-trip
tested against a handful of real and synthetic projects, not against the variety of games
that actually exist.&lt;/p&gt;
&lt;p&gt;It is also not affiliated with, endorsed by, or supported by EasyRPG. Their open
documentation of the format and their &lt;code&gt;liblcf&lt;/code&gt; source are the reason any of this was
possible, and their own &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https://github.com/EasyRPG/Editor&quot;&gt;Editor&lt;/a&gt; is the mature,
well-tested tool. If you have a project you care about, use theirs — and either way, keep
backups.&lt;/p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>A log for the tooling</title>
        <published>2026-09-02T00:00:00+00:00</published>
        <updated>2026-09-02T00:00:00+00:00</updated>
        
        <author>
          <name>C. Renaudo</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://carenaudo.github.io/carenaudo/blog/a-log-for-the-tooling/"/>
        <id>https://carenaudo.github.io/carenaudo/blog/a-log-for-the-tooling/</id>
        
        <content type="html" xml:base="https://carenaudo.github.io/carenaudo/blog/a-log-for-the-tooling/">&lt;p&gt;For most of what I build, something else already exists that would do the job. That is worth
saying up front, because “I wrote my own” tends to imply “nothing else would do,” and that is
rarely the honest reason.&lt;/p&gt;
&lt;p&gt;There are three reasons I actually end up writing the code.&lt;/p&gt;
&lt;h2 id=&quot;to-find-out-where-a-tool-fails&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#to-find-out-where-a-tool-fails&quot; aria-label=&quot;Anchor link for: to-find-out-where-a-tool-fails&quot;&gt;To find out where a tool fails&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;You can use a model quite happily until it hands you an answer you have no way to check.
Implementing it yourself — the property correlations, the solver, the assumptions the paper
does not restate because everyone in that subfield already knows them — is the fastest way I
know to learn what it does near the edge of its validity.&lt;/p&gt;
&lt;p&gt;Running Wilson against Abramzon–Sirignano against the classical D²-law on identical
conditions tells you something that reading all three papers does not. Not because the
papers are wrong, but because the disagreement between them only becomes concrete when the
same integrator, the same properties and the same initial conditions are feeding all three.&lt;/p&gt;
&lt;h2 id=&quot;because-the-thing-i-need-is-too-specific&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#because-the-thing-i-need-is-too-specific&quot; aria-label=&quot;Anchor link for: because-the-thing-i-need-is-too-specific&quot;&gt;Because the thing I need is too specific&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;General tools are general, and that is their virtue. But when the measurement is a
particular droplet geometry on a particular image series, or one instrument’s undocumented
binary format, the distance between what a tool gives you and what you need can be larger
than the distance between nothing and what you need.&lt;/p&gt;
&lt;p&gt;That is roughly where &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https://github.com/carenaudo/Menipy&quot;&gt;Menipy&lt;/a&gt; came from. A droplet
hanging from a needle has a shape, and that shape encodes a surface tension — but getting
from an image to a number means a contour you trust and a fit whose assumptions you can
state.&lt;/p&gt;
&lt;h2 id=&quot;to-learn&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#to-learn&quot; aria-label=&quot;Anchor link for: to-learn&quot;&gt;To learn&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Sometimes there is no better reason than wanting to know how the thing works, and I would
rather say that than dress it up as necessity. A fair amount of what is on my GitHub is
there because I wanted to find out whether I could.&lt;/p&gt;
&lt;h2 id=&quot;what-ends-up-here&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#what-ends-up-here&quot; aria-label=&quot;Anchor link for: what-ends-up-here&quot;&gt;What ends up here&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Four things, roughly:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Scientific computing.&lt;/strong&gt; The physics and the numerics: evaporation models, population
balance methods, shape metrics. What the equations actually do when you integrate them.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Engineering.&lt;/strong&gt; Parsing binary instrument formats, porting Python to Rust and keeping the
two at parity, building desktop UIs with &lt;code&gt;egui&lt;/code&gt; and Qt, and the unglamorous business of
packaging something a colleague can install.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;AI-assisted development.&lt;/strong&gt; I build a good deal of this by pair-programming with an AI
assistant, including an entire file-format-accurate desktop editor. I would rather write
about where that works and where it quietly does not than pretend either extreme.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Teaching.&lt;/strong&gt; I teach undergraduate fluid mechanics and particulate solids processing.
Reproducibility is a grading criterion, which turns out to be a good forcing function for
my own code too.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;the-rule&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#the-rule&quot; aria-label=&quot;Anchor link for: the-rule&quot;&gt;The rule&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Anything I claim here about a model should be traceable to the paper it came from, and
anything I claim about code should be reproducible from a repository. Where a result is
preliminary I will say so. Where I got something wrong, the correction goes in the same
place as the mistake.&lt;/p&gt;
&lt;p&gt;Notes as I go, then. Longer than a commit message, shorter than a paper.&lt;/p&gt;
</content>
        
    </entry>
</feed>
