Author: rdugger

  • From Pong AI to Play Store: How a Childhood Hobby Became a Rust Game Engine

    The first game I wrote with any real ambition wasn’t a game. It was a NEAT implementation that learned to play Pong. I wasn’t trying to ship anything — I was trying to understand how a system could learn to do something I taught it.

    That question has been running in the background of everything I’ve built since.


    TurboShells came next. I took the NEAT studies from PyPong and asked: what if instead of teaching an AI to hit a ball, I bred turtles? Each turtle’s body was drawn entirely from its genes — no sprites, pure math. Shell radius, leg length, color — all expressed from a genetic sequence at render time. They raced. The faster ones bred. The slower ones didn’t.

    Nobody played TurboShells. But I learned something: the genetics loop — dispatch a breeding pair, wait for the outcome, observe the consequence — was more interesting to me than any game mechanic I’d seen. I wasn’t building a racing sim. I was building a system that made things happen without me.


    ChimeraLab was the first time I tried to give the genetics a body.

    Custom 3D creature rendering. SpineComponents — oblongs stacked together, body parts articulated from code. I got a humanoid assembled. I gave it a skeleton. I ran the simulation.

    It fell to the floor under its own weight.

    I never did fix the bipedal problem. But I got it to transition from two legs to four using a slider, and watching that happen — a creature renegotiating its relationship with gravity in real time — taught me more about 3D rendering than any tutorial I’d read. Sometimes the failure is the lesson.


    rpgCore was the foundation I should have built first. A thousand tests. Real ECS architecture. Genetics, lifecycle, dispatch — everything composable, everything verified. SlimeGarden put it to work: an astronaut crash-lands on an unusual planet and finds slimes. Breed them. Dispatch them. See what comes back.

    It sounds simple. It wasn’t. And it pointed somewhere.


    Seven projects in, I was staring at the Google Play Store submission checklist.

    OperatorGame ran on Android. Real Rust, real Bevy, real APK on a real phone. The combat worked. The UI was clean. I’d solved the hard problems.

    The submission required a 512×512 app icon, a 1024×500 feature graphic, and two screenshots.

    I didn’t have any of them.

    I could have made them. It would have taken an afternoon. But sitting there looking at that checklist, I realized the assets weren’t the problem.

    The problem was I had no audience. I was about to pay the Play Store’s attention tax — discoverability weighted toward downloads, downloads toward reviews, reviews toward players who found you somehow — with zero players behind me. I wasn’t Android Store money-ready. I was Itch.io audience-ready.

    Those are different things. Confusing them is expensive.


    Here’s what the journey looked like from the inside:

    PyPong AI taught me how systems learn. TurboShells taught me that genetics loops are more interesting than game mechanics. ChimeraLab taught me that creatures fall down and that’s instructive. rpgCore gave me the foundation. SlimeGarden gave the foundation a story. OperatorGame proved the Android pipeline. VoidDrift took the dispatch loop — Scout mines ore, returns, consequence — and dressed it in something people want to watch.

    Every project is the same loop. Something dispatches. It does its work. It returns with a result. Something changes.

    I’ve been building that loop for years. I just didn’t see it until I looked at the list.


    The lesson isn’t “don’t aim for the Play Store.”

    It’s: know what you’re ready for. The Play Store is a distribution problem you solve after you have players, not before. Itch.io is where you find out if anyone cares. If they do, the Play Store is a next step. If they don’t, you learned that cheaply instead of expensively.

    VoidDrift is on Itch right now. A small audience that keeps coming back. That’s the signal I was missing with OperatorGame.

    When the audience is real, the Play Store assets take an afternoon.


    There’s a story that keeps circling my mind. Someone in a ship, traveling through a black hole, becoming something else. They find a station. What follows is a macabre exploration of self — what survived the transit, what didn’t, what the new thing is capable of.

    VoidDrift is the ship and the void. SlimeGarden is the crash-landing after.

    The loop doesn’t end at the Play Store. It ends when the story does.

    The Scout dispatch loop in VoidDrift is the same loop TurboShells was running — breed, wait, observe — dressed in space mining clothes, nine projects later. Phase 5 is live. The Play Store listing is three PNGs away.

    The story is still circling. I’m still building toward it.

  • I Built a CLI to Replace Expensive AI Directive Generation

    I Built a CLI to Replace Expensive AI Directive Generation

    The friction point is simple to describe. Claude designs the architecture. Windsurf builds it. The directive that connects them — structured, scoped, phase-gated — gets written by me, by hand, every single time.

    I’m the middleware. I built a tool to replace myself. It didn’t quite work.


    OpenAgent started from a real observation: the same context was being re-explained in every session. I had architectural patterns, stop rules, test floor conventions — and every new Windsurf session, the agent had no idea any of it existed. The directive was the missing connective tissue. Write it well and the agent stays on scope. Write it badly and the agent invents its own architecture.

    So I built a CLI that reads the codebase, understands the structure, and generates directives shaped to my development style. The breakthrough was SOUL.md — eight questions about how I actually work. That profile gets embedded in every directive. When OpenAgent generates something, it references the right conventions, names the right stop conditions. It sounds like something I’d write.

    It’s on PyPI as openagent-directive. v0.2.2. 103 passing tests.


    Here’s the part I didn’t put in the README: I still do the same manual cycle.

    The tool works. The directives it generates are useful. But I’m still the one handing them to Windsurf, watching the session, course-correcting when it goes sideways. The friction I wanted to eliminate is still there because the real blocker isn’t the directive — it’s that there’s no coding agent that lives outside an IDE.

    Windsurf, Cursor, Copilot — they all require a human in the seat. The autonomous loop I wanted, where OpenAgent feeds a directive to a coding agent that executes independently, reports back, and waits for the next one, doesn’t exist yet in any reliable form. The IDE-bound constraint kills the automation before it starts.

    I built a correct solution to the wrong layer of the problem.


    The pivot I keep thinking about: OpenAgent as an MCP tool. Not a CLI that generates directives for humans to hand off, but a codebase intelligence layer that a coding agent can query directly. What files are in scope? What’s the test floor? What patterns does this codebase use? An agent with access to that context doesn’t need a human to write the directive — it can construct its own.

    That version of OpenAgent is waiting on the ecosystem. When a capable coding agent exists that can operate outside an IDE, receive a structured task, execute against a real codebase, and return proof — OpenAgent is already positioned to be the interpreter it needs.

    For now it’s a CLI on PyPI that saves me twenty minutes per directive and reminds me that some problems can’t be fully solved until the infrastructure around them catches up.

    The friction is still there. The tool is ready when it isn’t.

  • Building a Mobile Idle Game in Rust/Bevy Without a Game Engine Background

    Building a Mobile Idle Game in Rust/Bevy Without a Game Engine Background

    The line appeared one night and wouldn’t leave:

    The station has been here longer than you. It should have been consumed. It has not been. You don’t know why.

    I didn’t know what kind of game it belonged to. I spent months carrying it before I found out.


    VoidDrift is a mobile idle game where you mine asteroid debris at the edge of a black hole. You build a drone fleet. Factions you don’t understand start sending messages. There’s no win condition. The horizon is a one-way membrane.

    I had no game engine background when I started. I’d built systems — ECS architectures in Python, simulation loops, procedural generators — but I’d never shipped a game. I picked Rust and Bevy because the pipeline I needed (Android and WASM from a single codebase) pointed there. The ECS paradigm took time to click. My first systems were monolithic messes that tried to own too much.


    The breakthrough wasn’t a technical insight. It was administrative discipline.

    I forced myself to write an ADR — an Architecture Decision Record — before touching any major decision. ADR-003: bevy_egui for all HUD because Mali GPU stabilization on Android required it. ADR-007: system partitioning because Bevy’s scheduler hits a 20-tuple limit faster than you expect. Every constraint that would have sent me into a three-day refactor got documented before it became one.

    Bevy 0.15 with bevy_egui 0.33 has a specific problem nobody warns you about: egui::Window click events are broken in the Update schedule. The fix is painter + ui.interact(). I know that because I hit the wall, diagnosed it, and wrote it down. The ADR system means I don’t rediscover the same walls twice.


    What didn’t work: scope. I wanted branching faction storylines, a full three-ring resource economy, Mk II drone tiers, a complete Human-versus-Signal narrative arc. None of that is in the current version. What shipped is a focused mining loop, a production tree, and enough faction voice to suggest something larger without explaining it.

    The unexplained parts are intentional now. They weren’t when I started.


    VoidDrift is live on itch.io — Android and WASM from the same codebase. 505 views, 10.4K impressions. A small audience that keeps coming back.

    The Play Store is three assets away. An app icon, a feature graphic, two screenshots. Not a code problem. An afternoon problem I haven’t made time for yet.


    The station is still there. The factions are still watching. The black hole is still waiting.

    Some games tell you everything. VoidDrift tells you enough to make you wonder about the rest. That constraint — deliberate incompleteness — turned out to be the best decision I didn’t plan to make.

  • What Ants Taught Me About Systems Design

    What Ants Taught Me About Systems Design

    Before programming, there were ants.

    Not metaphorical ones. Real colonies — pheromone trails that appear from nothing, queen mortality absorbed without panic, chambers organized by workers who’ve never been given instructions. No central controller. No plan. Just rules simple enough to follow and complex enough to produce something alive.

    When I started building software, I was chasing the same thing.


    AntSim is an evolutionary ant colony simulation with split-view rendering. The top two-thirds is the foraging ground — pheromone trails building and fading in real time. The bottom third is the colony cross-section: chambers, tunnels, brood, storage.

    The genetics system controls five traits per ant: sensitivity, speed, boldness, lifespan, energy efficiency. That’s it. Five genes. The colony doesn’t know it’s evolving. Individual ants don’t know they’re part of a system. They follow their traits and the colony either survives or it doesn’t.

    When a queen dies — randomly, the way queens do — workers autonomously identify royal jelly candidates and raise a successor. Colony continuity without external control. The system just continues.


    The original genetics system had too many genes. Forty variables, every trait interacting with every other. The ants evolved into creatures that couldn’t survive their own complexity — biologically sophisticated and functionally useless.

    Stripping back to five traits was uncomfortable the way all simplification is uncomfortable. It felt like giving up. What I got instead was emergence — behaviors I didn’t design, produced by interactions I didn’t anticipate, between five simple numbers and enough time.

    The cross-section visualization is still in progress. Phase 3 rendering complexity exploded the moment I made it detailed. Individual chambers, tunnel geometry, precise brood locations — the simulation could handle it but the renderer couldn’t. I scaled back. It’s still on the list.


    Here’s what I didn’t understand when I started: I wasn’t building a simulation. I was building a philosophy.

    Every system I’ve built since has the same underlying shape. Something dispatches — an ant, a Scout drone, a queued job, a breeding pair. It does its work without supervision. It returns with a result. The colony, the station, the codebase, the task queue — they change accordingly.

    That loop appeared first in an ant colony. It’s in VoidDrift’s Scout dispatch. It’s in PrivyBot’s job queue. It’s in rpgCore’s genetics engine. I’ve been building the same system for years in different materials.

    Emergent behavior doesn’t require complex rules. It requires simple constraints, honest consequences, and enough time to surprise you.

    The ants figured that out long before I did.

  • The Engine Legacy: From Asteroids to rpgCore

    The Engine Legacy: From Asteroids to rpgCore

    At some point I stopped counting how many times I’d written the same movement system.

    It wasn’t any one project’s fault. Every game had its own physics, its own collision logic, its own state management — each a reasonable decision in isolation, collectively a pattern I was tired of repeating. The tenth time you write an entity update loop you start asking a different question: not “how do I build this game” but “what would make the next one easier to start.”


    rpgCore started as a refactoring project and became something else entirely.

    The idea was simple: extract the patterns that kept repeating and build a reusable toolkit. A simplified Entity-Component-System in Python — modular enough that adding a genetics system didn’t require touching the movement code, adding a lifecycle system didn’t require touching the genetics code. Each piece independent, each piece composable.

    Simple ideas scale badly. The toolkit grew. A thousand tests. Then eleven hundred. GeneticsComponent. LifecycleComponent. DispatchSystem. ResourceFlow. A scene system, a UI theme, a demo registry. At some point it stopped being a toolkit and started being a codebase worth protecting.


    The mistake I kept almost making was treating rpgCore as a means to an end. It isn’t. The foundation is the thing.

    When you build something generic enough to support any idea, you start seeing which ideas are worth having. The genetics system in rpgCore is the same system that ran TurboShells’ turtle breeding. The dispatch pattern is the same one VoidDrift’s Scout drones use. The resource flow is the same loop that appears in SlimeGarden. rpgCore didn’t just save me from rewriting code — it made the pattern visible, and once the pattern is visible you can apply it deliberately.

    The 1,122 tests aren’t there because I’m disciplined. They’re there because a foundation without verification isn’t a foundation, it’s a guess.


    The BreedingSystem is still queued. Allele resolution, trait inheritance, phenotype expression from genotype. When it ships it connects to every genetics-adjacent idea I’ve had since TurboShells. ConquestSystem is queued behind it.

    rpgCore doesn’t ship. It makes shipping possible.

    That’s a slower kind of value than a finished game. It’s also the only kind that compounds.

  • The Hybrid Engine: Rust Performance, Python Agility

    The Hybrid Engine: Rust Performance, Python Agility

    The problem with DeFi trading bots is speed. The problem with fast code is that it’s expensive to change.

    A pure-Rust bot wins the race to the block — compiled, deterministic, fast. When the market shifts and your strategy needs to change, you recompile. Overnight. While your edge evaporates.

    A pure-Python bot iterates in minutes. It also loses to anything compiled. In a system where the difference between capturing an arbitrage and missing it is milliseconds, interpreted code is a structural disadvantage.

    I needed both. So I built a bridge.


    The hybrid architecture splits responsibility at the right seam. The Rust core handles everything where latency matters: WebSocket connections, memory-safe transaction signing, packet serialization. Compiled, stable, rarely touched. The Python strategy layer sits above it, communicating through a lightweight interface. When the trading logic changes — when a pattern emerges, when a parameter needs tuning, when a strategy turns unprofitable — you change the Python. No recompile. The execution layer keeps running.

    Decoupling execution from intelligence meant iteration speed became unconstrained by compilation time. A new strategy at midnight, tested by 2am, discarded by morning without touching Rust.


    The bridge itself was the hard part. Any interface between two languages has a seam, and seams are where bugs live. Getting data structures consistent on both sides — ensuring what Rust serializes is exactly what Python expects — required more care than either side alone. When something went wrong, it could be Rust, Python, or the interface between them. You learn to test both sides independently before trusting the combination.


    PhantomArbiter ran 400 live trades on Solana in 2025. The architecture worked. The margin didn’t scale — the arbitrage windows were narrower in practice than in theory, and at volume the economics didn’t justify the infrastructure.

    But the pattern was correct. Compile what doesn’t change. Script what does. The intelligence layer should be easy to replace. The execution layer should be hard to break.

    That principle didn’t stay in trading. It’s in every complex system I’ve built since — MCP tools handle execution, the model handles intelligence, and the interface between them is where the design lives.

    I didn’t keep trading. I kept the pattern.

  • The Insight Lens: Visualizing the Dialer Blindspot

    The Insight Lens: Building Chrome Tools for Departments

    Most workplace software was built for someone else’s workflow.

    I work in high-volume digital marketing. The tools we use — Convoso, Telesero, various CRMs — are built for general audiences, which means they’re never quite right for any specific team’s actual process. Managers get report portals that are hard to read and impossible to print. Agents get interfaces with small frictions that compound across hundreds of calls a day. Scheduling runs on whatever the vendor decided was sufficient.

    So I build tools.

    ConvosoInsightLens started as a simple request: the Scheduling Manager needed to present agent performance reports to stakeholders, and the Convoso portal printed badly. I built a Chrome extension that reformats the output into something clean and readable. The vendor’s UI is still clunky. The reports look professional now.

    The page refresher was even simpler. Scheduling needed an auto-refresh tool. The right answer wasn’t a third-party extension — those tools read browser data, and I wasn’t willing to risk company information for convenience. The private version took ten minutes to build and doesn’t touch anything it doesn’t need to.

    The dialing tools are the ones that add up. An agent on Telesero or Convoso might handle 150 to 400 calls in a day. Shaving three seconds off each one recovers somewhere between seven and twenty minutes per agent per day. I’ve built several of these — small quality-of-life upgrades that make each dial slightly faster, each disposition slightly cleaner. Across a team, the math compounds.

    CB Huddle was the ambitious one: a remote coaching tool to bridge a feature gap between Telesero and Convoso. Still in progress.


    The lesson isn’t about Chrome Extensions. It’s about proximity. Being close enough to the actual work to see the friction, and skilled enough to do something about it. That combination — operational context plus technical ability — is rarer than either one alone.

  • From Genetics to Tactics: How a Breeding System Became a Squad Game

    From Genetics to Tactics: How a Breeding System Became a Squad Game

    SlimeGarden had a good idea: crash-land an astronaut on a strange planet, have them breed and dispatch slimes, watch the genetic loop produce something you didn’t design. The mechanics were there. The world wasn’t quite right.

    So it evolved.


    OperatorGame is what SlimeGarden became when I pushed the genetics into a tactical direction. Instead of slimes, genetically unique operators. Instead of a crashed astronaut, a squad commander deploying crews to a resonant planetary surface. The core loop shifted from “breed for optimization” to “assemble for mission success” — still dispatch-based, still consequence-driven, but with a layer of strategic intent the breeding sim never needed.

    The architecture held up. 145 unit tests. 52+ Architecture Decision Records — one for each decision that could have sent the codebase sideways. Lock the genetic engine first, build the dispatch simulation as a separate layer that consumes genetic traits as inputs. Systems that shouldn’t couple don’t.

    The Android pipeline worked. Real Rust, real cdylib, real APK on a Moto G 2025. Wall-clock async timers that survived background processes, sleep states, and WASM contexts well enough that the edge cases weren’t blockers.


    The game stopped just short of the Play Store. Not because of code. Not because of architecture. Because somewhere in the process the compelling reason to keep playing hadn’t fully materialized. The systems were sound. The experience they produced wasn’t yet what the concept deserved.

    There’s a version of that decision that feels like failure. There’s another version where you recognize you’ve built the proof of concept the next project needed.

    VoidDrift went straight to itch.io and skipped the Play Store entirely — that was the lesson OperatorGame taught. The pipeline works. The audience question is separate. You don’t need a Play Store listing to find out if anyone cares. VoidDrift is live. A small audience that keeps coming back.


    OperatorGame could join it on itch.io. The Android build exists. The pipeline is proven. Three assets from having a page — an icon, a feature graphic, two screenshots. Whether the resonant planetary surface finds an audience is a question only publishing answers.

    The idea that started as SlimeGarden is still alive. It just hasn’t shipped yet.

  • Why I Put a Genetic System in a Turtle Racing Game

    Why I Put a Genetic System in a Turtle Racing Game

    The NEAT algorithm that taught a paddle to play Pong is the same algorithm that maps genetic traits. Someone pointed that out and I couldn’t stop thinking about it.

    TurboShells started as a question: what if the turtle’s body came from its genome? Not as an abstraction — literally. The genome string B1-S2-P0-CFF0000 encodes four values: body type, shell type, pattern, color. The frontend parses that string and assembles the turtle from layered sprites with dynamic tinting. Change the genome, change the animal. Every turtle on the roster looks different because every turtle is different — genetically.

    That’s the Paper Doll system. A compact encoding that drives visual rendering, persists across races, and accumulates history. A turtle that wins races has a record. It has traits. It has a string that says exactly what it is.


    The project grew. What started as a breeding simulation became a real-time multiplayer racing game: FastAPI backend running 60Hz physics, WebSocket broadcasting race state at 30Hz, React and PixiJS on the front end interpolating between ticks. An NPC Manager generates persistent AI turtles that populate the roster when human players aren’t racing.

    The architecture is genuinely layered — simulation core, server bridge, frontend rendering each in their own boundary. The genome doesn’t know about rendering. The race engine doesn’t know about WebSockets. The Paper Doll assembler doesn’t know about physics.


    The lesson from PyPong was that the interesting thing about NEAT wasn’t the Pong — it was the emergence. Random variation, selection pressure, something that looks like intelligence appearing from simple rules.

    TurboShells is the same idea with different materials. The turtle’s genome isn’t optimized by NEAT weights. It’s expressed as a visible body, a race record, a persistent identity. You’re not watching neural networks compete — you’re watching genetic variation play out in real time across a roster of animals that have history.

    That’s the loop I keep building. Different game, different encoding, same underlying question.

  • Teaching Pong to Play Itself: My First Neural Network Experiment

    Teaching Pong to Play Itself: My First Neural Network Experiment

    Pong is the right choice for a first experiment because it has almost no variables. Two paddles. One ball. If you can’t teach an AI to play Pong, you can’t teach an AI anything.

    I used NEAT — NeuroEvolution of Augmenting Topologies. It doesn’t just adjust weights on a fixed network structure. It evolves the topology itself, starting minimal and adding complexity only when it helps. The training runs headless at 500x real-time speed; a separate visual mode exists purely to verify that what trained actually works. Generation 0: random paddle movement, 0% win rate. Generation 50: 98% win rate, predictive tracking.

    The difference between reacting and anticipating is memory. Standard feedforward networks see the current frame. Recurrent Neural Networks carry memory of previous states — ball velocity, trajectory history. That’s what gives the Gen 50 agent its characteristic quality: it moves to where the ball will be, not where it is. The RNN is what upgrades NEAT from “learns to respond” to “learns to predict.”


    The first training approach was pure ELO. Score points, survive, reproduce. The population converged fast — too fast. By generation 20, every agent played the same way. Safe returns, center positioning. They’d found a local maximum and stopped. No one was discovering anything.

    Novelty search fixed it. Instead of rewarding only performance, you reward uniqueness — points for behaviors the population hasn’t tried. The diversity pressure kept agents exploring. Agents with strange positioning, unusual angles, aggressive strategies started appearing — and some of them turned out to be genuinely superior. The “wrong” strategy was actually better. Pure optimization would never have found it.

    Any system without diversity pressure converges on the same answer. It finds the local maximum and calls it done. That lesson applies well beyond neural networks.


    What didn’t work: high mutation rates to accelerate training. The population collapsed — agents changed faster than they could build on what worked. Every generation erased what the previous one had learned. Slowing it down made the evolution meaningful. Some processes can’t be accelerated without destroying the thing that makes them work.


    This was the first project. Everything since has the same shape: variation, selection, emergence you didn’t design. TurboShells encoded the same loop into turtle genetics. rpgCore formalized it into a composable system. VoidDrift runs it as a drone dispatch loop.

    The Pong agent that discovered a non-obvious return angle at generation 47 is the ancestor of all of it. I just didn’t know that yet.