26-AUG-2026

26-AUG-2026

0940

I’m taking some vacation before Mayhem heads off to kindergarten. This morning is orientation and she is nervous and excited and off to orientation this morning so I had some time to do some review of lurtz, and something cool happened.

The design of lurtz has always been a little haphazard owing to the vibe-y nature of it, I knew that going in. I also knew that it was likely I would step over the SRD/PHB line as I am sure Claude is trained – like all the perpetrators of the grandest larceny in the history of theft – on all the PHB stuff, and it was going to leak in. My understanding of the SRD and OGL and all that is this:

  1. Build whatever you like, these things govern what you distribute.
  2. Distributing stuff based on the SRD content is fine.
  3. If you distribute PHB content, or any other content from WOTC, the full force of the legal department of Hasbro will set ruin upon thee and thy house for ten generations.

I do not want to piss off Hasbro, but I am sure lurtz has like, PHB and some Arcana and such.

Now, this may seem like a problem if you want to build a D&D engine to manage balance for all of D&D. I agree it is a constraint, but lurtz can be clever, and part of why I vibecoded it was to sort out how best to design around this.

I knew I wanted a bytecode machine, I wasn’t sure why. I try not to make design decisions like that without having a reason more than, “Because they’re cool.” When I first started learning Rust, I tried the synacor1 challenge, which required building a weird little bytecode machine among some other fun puzzles I won’t spoil. I’ve really liked these little guys since taking a low-level systems course in college based on some weird little 16b ISA that had a little simulator and stuff, it was pretty rad. I liked the architecture enough I started building hazel along those lines, before getting distracted by other shinies and landing here.

In any case, I knew I wanted to take that approach, but that didn’t really satisfy me for a reason, I decided eventually though that a good enough reason was to see how the tokenmachine managed with the architecture.

Overall, fine, I guess. Not great, not terrible. It took a lot of nudging to push it towards that model, and it took a lot more to keep it there and get it not to re-invent every single wheel. Normally I think these systems have good API-sense, but in reality what they have is majority API sense. That is, whatever the greatest common subset of APIs people use, that is what it will inevitably lean to use; and since the APIs themselves are probably part of it’s training set, there is a huge sample of DIY in there. Since the sorts of libraries for this less common architecture aren’t as readily available or used as libraries for a more ‘ad hoc/amorphous’ architecture2, it is more likely to grab a generic, frequently used library for which it has lots of examples, or just DIY everything. I had to point it to bitmask a bunch of times.

As I was starting to dig through some of the generated code, I started seeing it – PHB-sign. A little scattering of Battle Master rules has snuck it’s way into the API. I need this code, it’s not going anywhere, by #1 above I can keep it I just can’t give it away, so the obvious solution is to stick it in a crate. In a lurtz-fighter crate it is safely tucked away where I can avoid publishing it, but how can I keep it’s PHB-ness off the core engine?

When you design an ISA, you don’t take responsibility for every program written in that ISA, you just design the ISA. When Intel had its FDIV bug, it was responsible because the ISA was broken. When your software crashes because someone divided by zero, the author of the software is at fault, not Intel.

In the same way, lurtz doesn’t need to provide direct APIs for any of the mechanics – in any system – lurtz-as-a-VM should be agnostic to the game system entirely. We need that for 2014/2024 support anyway. In a way, you can think of a battle as an incremental compilation problem. We have a battle-log, I need to turn that sequence of actions into a sequence of opcodes on a VM such that the state of the VM after executing those codes contains sufficient information to reconstruct the battle at that state.

That is to say, I can manage some auxillary structure to manage all the things a Battle-Master class would need without actually implementing the class directly in lurtz. This is a much better reason and I retroactively use this one as justification for this model. It has some nice knock-on effects too.

Right now lurtz is one big crate with everything piled in. It has an action API for all the things you can do in D&D, it has the engine itself, all the opcode stuff, everything, one big pile. Obviously I can split out lurtz-$class like mentioned, but also lurtz-monsters and lurtz-players can be split out. Players and Monster statblocks both translate to ‘intialization’ opcodes that start a battle log and configure the environment. These translate into datastructures inside the VM as well. I can then further split out lurtz-srd, which contains the core SRD actions, upon which all these other crates depend, now lurtz is the core crate with only the engine in it. Each crate can have it’s own validation set, which we compile and assert on the engine state. These now track the implementations much more tightly, which will greatly improve my ever growing mutant problem.

It has a natural extension pattern as well, new UA or splatbooks can be included as another crate, independently tested. It means that lurtz can be optimized aggressively for the GPU and the ‘compiler’ crates can expect to run more or less uninterrupted on the CPU. I suppose in the final tally the training process probably looks like some number of agents playing as many games as they can on the GPU; they make a big pile of next move decisions while the CPU grabs those moves, compiles them, and sends them back to the engine to update in parallel. I’m not sure if I could get 100% of that loop on the GPU but it doesn’t seem impossible. That’s either inexperience or very clever, I’m not sure which.

It also leaves open a door to test other combat systems. Pathfinder is an obvious target, but there are lots of systems that look like D20-style combat.

All this would’ve probably been findable with a whole bunch of anxious thought about how things might work. By the time I got there I’d’ve been so worried about implementing it wrong I’d’ve given up. I’ve put about two proper weeks of thought into lurtz at this point and have a working program. It’s not good, it’s not super well factored (yet), but it’s reasonably functional and it’s easy enough to change. Because I had a working program, I was able to see this refactor, instead of having to imagine it ahead of time. I can look at this code and this structure falls out.

I will definitely need to rewrite every one of these crates at some point3, but having them to hand when finding the architecture makes code feel much more play-doh than it used to.

I quit working on hazel partially because I was getting lost down a rabbit hole of screwing with the trait system to do some weird message passing stuff. I also quit on it because I kept running into problems that needed a big fat refactor that would take days of churning over error messages and making tiny adjustments. If it weren’t for the fact that I do want to keep one project as agent-free as I can, I’d be using an LLM for those changes today and probably still be working on it. That hypothesis is supported by this result with lurtz. My favorite part of coding is refactoring, because it is really difficult to hold both the observed and the desired in your head at the same time; much less to plot a course from one to the other. My strategy has been to rely on good observation skills and git to slowly move from one to another. LLMs offer a new option here. lurtz has been sort of ‘3d printed’ into place. It’s cheap plastic, it’s all one mold instead of several moving parts, but it gives me an object I can hold in my hands.

I occasionally turn wood; mostly to make handles for stuff. One of the things about turning handles is that – while you need a template to get a repeatable result, no two handles are ever finished exactly alike. You always use the same process, or at least I do:

  1. Turn stock to the template dimensions
  2. Cleaning pass with a skew
  3. Rough pass with 60 grit
  4. Stop lathe, feel the handle.
  5. Start lath, skew or sand.
  6. Repeat 4 and 5 until it’s right, adjust grit as necessary.
  7. Friction polish with BLO, Wax on the Lathe, Buff.

You can’t make a handle for something without trying to use it first, there is no substitute for holding it in your hand. Trying to divine this architecture for lurtz ahead of time, especially given my vanishingly close to zero prior experience in game making, would’ve been like trying to make a perfect handle template – not possible.

In any case, this was a very nice validation of what I hoped would happen when I started this, I had hoped that LLMs would work nicely this way and it has. I have a natural skeleton to build the next version of this against, and all it took was two weeks of prompting, not exclusively on this but majority on this, most of which were status report requests / disambiguating parts of the plan. I’ll talk about how I set up this project another time. I kept all the conversations archived so I should be able to do some forensics on it.

  1. The original challenge page is down, this is a mirror I found here. I hope it comes back at some point, but maybe someone oughta just build a new one. :)

  2. I couldn’t find a way to make that not sound perjorative. I don’t mean it that way. Ad hoc architecture is not a crime, it’s a choice with trade-offs.

  3. I wrote that and immediately went to hedge – maybe this is true, but I could see a world where I rewrite some of the crates, then make the machine copy that style to the others. Still a rewrite, but by what fraction?