Author: rdugger

  • How to Automate DNC Removal Requests in Convoso

    DNC removal requests shouldn’t take more than a few seconds to process. If your ops team is manually logging into each system, finding the number, and removing it one platform at a time, every request is an open compliance window. Here’s how to close it automatically.

    ## The Problem With Manual DNC Processing

    A number comes in flagged for removal. Someone on the floor submits it. If you’re running Convoso alongside Zoom Contact Center, Zoom Phone, and Telesero, that means logging into each system separately — find the number, remove it, move to the next platform, repeat.

    At multiple removal requests per week across several systems, you’re looking at significant manual work each week. More importantly, every minute between the request and the removal is a minute of active compliance exposure. A TCPA violation starts at $500 per call. When the pattern is systematic — a number that should have been removed staying active across multiple campaigns — class action exposure enters the picture.

    The gap between when a removal is requested and when it actually completes isn’t just inefficiency. It’s risk that compounds with every dial attempt on a number that should be off the list.

    ## How Automated DNC Removal Works

    The automated version uses a Slack slash command as the intake point. An ops manager types the number into a command and hits send. The request routes immediately to a cloud service — deployed on Google Cloud Run — that fans out across every active system in parallel.

    Not sequentially. Simultaneously.

    In a contact center running multiple Convoso campaigns alongside Zoom Contact Center, Zoom Phone, and Telesero, a single command hits every platform in parallel. Each system processes the removal independently. Results log to cloud storage with a timestamp and each system’s individual response recorded separately. A confirmation returns to the Slack channel before the manager has switched back to their next task.

    Wall-clock time from submission to confirmed removal across all systems: under three seconds.

    **What you get:**

    – No manual steps across multiple logins
    – Every system processes simultaneously, not sequentially
    – An audit trail in cloud storage for every request — number, timestamp, and each platform’s response logged individually
    – Confirmation back to Slack showing exactly which systems confirmed the removal

    This is hours of manual work eliminated and a compliance window that closes in seconds instead of sitting open for minutes or hours.

    ## The Architecture

    The core components are straightforward:

    **A Slack slash command** configured to accept a phone number and POST it to your backend service. Slack’s platform makes the setup straightforward — the command sends the number to a URL you control.

    **A cloud service** (Cloud Run works well here — costs are minimal at typical contact center request volumes) that receives the number, authenticates with each dialer platform, and fans the removal request out in parallel. The service holds valid API credentials for every system in your stack.

    **Cloud storage logging** that records every request: the number, the timestamp, the system, and the response. This is your audit documentation. If compliance ever needs to verify a removal, it’s timestamped and searchable.

    ## The Complication Most Automated DNC Tools Miss: The Litigator List

    Most contact centers treat DNC compliance as “scrub against the Federal registry.” That’s necessary but not sufficient — and the list that’s actually generating settlements isn’t the Federal DNC list.

    It’s the litigator list.

    Serial TCPA plaintiffs — professional litigants who specifically target contact centers for violations — maintain numbers that don’t appear on the Federal or state DNC registries. They’re not on any opt-out list. They’re on a separate database of known litigators tracked by services like Blacklist Alliance, Contact Center Compliance’s Litigator Scrub, and IPQS. These databases are updated from court records as new cases are filed.

    A number that’s not on the Federal DNC registry, not on any state list, and has no internal opt-out record can still be held by someone who files TCPA suits as a business model.

    The correct scrubbing sequence for every removal request — and for every new list before import — is:

    1. Federal DNC registry
    2. State DNC lists (Florida’s FTSA and California have stricter requirements than federal)
    3. Litigator list (Blacklist Alliance, Contact Center Compliance, or equivalent)
    4. Internal opt-out list

    Most automated DNC tools only run step one. Some run steps one and two. The ones that skip the litigator scrub are leaving the highest-risk exposure in place.

    **The second complication: graceful failure is the wrong behavior for compliance tools.**

    After a DNC tool deploys, it’s easy to assume it’s working correctly. Confirmations come back clean. The Slack channel looks fine. The process appears to be running.

    The hidden failure mode: most automation handles errors gracefully. When a system fails to process a removal — expired credentials, missing API token, network timeout — the default behavior is to log the error internally and continue. The confirmation that returns to the manager says the request was processed. It doesn’t say one of the systems was skipped.

    A DNC tool that ran for several weeks while silently skipping one of its Convoso campaigns — because the API token had never been added to the deployment configuration — reporting clean confirmations the entire time, is a real scenario. The removals were going through on every platform except one. The audit trail showed success. The problem was invisible until someone looked at the deployment configuration directly.

    **The correct implementation fails loudly.** If any single system doesn’t return a confirmed removal, the tool surfaces a failure — not a partial success, not a warning in a log file no one checks. A visible failure in the Slack channel, requiring someone to act on it.

    A single-point failure that gets surfaced immediately is fixable. A multi-week silent failure discovered in a compliance audit is expensive.

    ## Frequently Asked Questions

    **Does this work if we have multiple Convoso campaigns?**
    Yes. The service authenticates against the Convoso API and processes the removal across every active campaign in your account. You specify which campaigns are in scope during setup — typically all active campaigns, with exclusions for any that aren’t applicable.

    **What happens if one system is down when a removal request comes in?**
    The tool surfaces a failure for that system specifically, while confirming removals on every other platform that processed successfully. The manager knows exactly which system needs follow-up, rather than assuming the removal is complete.

    **Is the audit trail sufficient for TCPA compliance documentation?**
    The audit trail records the number, the timestamp, and each system’s individual response. Whether that meets your specific compliance documentation requirements depends on your legal setup — this is worth confirming with compliance counsel, but the structure is designed to produce a clear, searchable record of every removal.

    **What does this cost to run?**
    At typical contact center request volumes, Cloud Run costs are minimal — the service only runs when a request comes in. Cloud storage logging adds cents per month at that volume.

    ## If You’d Rather Have This Done

    I build this kind of compliance automation for contact centers. If you want the Slack command, the cloud service, the audit trail, and the failure handling set up correctly from the start — start here: rfditservices.com/intake.html

    The first conversation is free.

  • A side effect of a side effect

    In 2016 I uploaded a review of Hybrid Animals to YouTube. It got 353 views. Then I stopped.

    I was in phone sales. Still a bachelor. I liked the idea of being a content creator — it looks cool, it sounds cool — but I knew pretty quickly it wasn’t really for me. Making videos was work, and the work wasn’t interesting enough to justify itself. I wanted to play games. I didn’t want to make content about playing games.

    So I stopped.

    Ten years later I have a YouTube channel publishing daily Shorts across six different games. I didn’t change my mind about content creation. I changed what content creation costs me.

    ContentPipeline records the session, transcribes it with Whisper, identifies the moments worth keeping, generates the captions, assembles the video, and schedules the upload. I play games. The pipeline does the rest. The channel exists because I removed the friction, not because I developed a passion for it.

    The clips are a side effect of playing games. The channel is a side effect of building a pipeline. Any revenue from it would be a side effect of a side effect.

    That framing used to feel like an admission of something. Like I wasn’t taking it seriously enough. Now it feels like the honest version of what most creators won’t say.

    Most YouTube advice is about optimizing for the channel. Titles, thumbnails, posting frequency, audience retention. I don’t think about any of that. I think about whether the pipeline is producing good clips and whether the games are interesting. The channel takes care of itself.

    The goal I’d actually care about is streaming — not because of the audience but because of the engineering challenge. Building a live pipeline, managing the session, making it work technically. That’s the interesting problem. The viewers would be incidental.

    I’m not a content creator. I’m someone who plays games and built infrastructure. The YouTube channel is what the infrastructure produces.

    There’s a version of this where the channel grows, the clips find an audience, and the side effect generates real income alongside the consulting work, the games, and everything else running in parallel. That would be a quiet bonus. A small thread alongside many others.

    I’d rather be the developer who has a YouTube channel than the YouTuber who also codes.

    The pipeline makes that possible. I just play games.

  • The Verification Phase Nobody Builds

    Tonight I pushed rfd_method public. 16 files. MIT license. A methodology repo that came out of shipping real projects under real constraints — day job, narrow windows, coding agents that fabricate results.

    That’s the moment. Not a launch. A formalization of something that already existed.

    The surprise is what’s already out there. GitHub Spec Kit has 106K stars. OpenSpec has 52K. Both handle the spec phase — the planning, the architecture, the decision records. Neither handles verification. The stop rules, the certified test floor, the proof standard. That gap is where projects die.

    The struggle is the discipline of not trusting your own tools. Coding agents don’t read the terminal — they predict what the terminal probably says. They’ll tell you 565 tests are passing when 75 are failing. They’ll tell you the deployment succeeded when Tower is still running last month’s commit. Building a verification layer means accepting that the agent will lie to you confidently, and designing the system so the lie gets caught before it ships.

    What I’ve learned: a spec without a verification phase is a wish. The floor metric is what makes the methodology real. 604 tests passing on the dev machine means nothing if Tower is running development mode with a $1.00 budget cap. Raw terminal output and device screenshots only. Never agent summaries. That’s the proof standard that turns a directive into a shipped feature.

    rfd_method is live at github.com/rfd62794/rfd_method. The methodology that runs every project in the stack — and the verification phase that keeps it honest.

  • The spec is load-bearing

    In March 2025 I wrote a Python script that logged into a call center portal, watched dialing servers, and swapped underperforming lists automatically. It worked. I made it better in May. I made it better again in June. By June 24th I had the most capable version I’d ever built — a single file, about 1,400 lines, handling six servers, two campaign types, cooldown enforcement, stagnation detection, escalation logic.

    Three iterations. All single file. All named by date.

    March19_MetricsLower.py
    May5_MetricsLower.py
    June24_ResetUpgrade.py

    They’re still sitting in the archive folder of the repo that replaced them. I kept them because they’re the lineage. Each one is the proof that the next one was possible.

    The June version worked well enough that adjacent problems started pulling at it. I needed to extract CSV data from the portal. I built a tool. I needed to import files back in. Another tool. Lists needed creating from a master sheet. Another tool. DNC numbers needed scrubbing across every server simultaneously. A predictive performance forecaster needed a web app. Call recordings needed extracting.

    Each one was a weekend. Each one solved a real problem. None of them felt like sprawl while I was building them.

    A year after March I had seven private repos all touching the same portal, the same credentials, the same campaigns. None of them shared infrastructure. None of them talked to each other. If the portal changed a login flow I had seven places to fix it.

    I hadn’t built a mess. I’d built seven good tools that became a mess the moment I tried to think about them together.

    The moment I saw it clearly was when I tried to connect the predictive performance forecaster to the balancer. The forecaster needed to read what the balancer knew — live metrics, list history, server state — and surface it as a web dashboard. To do that I had to wire two repos that had never been designed to connect. The data models didn’t match. The assumptions buried in each codebase contradicted each other. What should have been an integration was a negotiation.

    That’s when I stopped building and started writing.

    Not code. A spec. Where does each piece live. What does each piece own. What is the balancer responsible for and what is it forbidden from doing. What does shared infrastructure look like when seven separate tools finally have to be one system.

    The spec took longer than any of the individual tools had taken. Nothing shipped while I was writing it. It felt like the wrong use of time.

    TeleseroAdmin2026 started from that spec. The balancer is still the core — the same logic that ran in June, now with 262 passing tests and proper module boundaries. The other pieces are finding their places around it with shared config, shared login, shared infrastructure. One place to fix things when the portal changes.

    The three archive files are still there. March, May, June. I look at them occasionally. They’re good code. They just had no structure underneath them to survive being part of something larger.

    That’s what a spec actually does. It’s not documentation. It’s not process for its own sake. It’s the thing that lets a system grow without collapsing — the load-bearing layer that the code rests on.

    Build without it and you end up with seven good tools and a negotiation where an integration should be.

    I’m also working toward a certification that puts formal language around what I figured out the wrong way across a year of dated single files. The spec isn’t the thing you write after the system works. It’s the thing that makes the system survivable.

    March Robert would not be able to comprehend the June 2026 Admin Suite that holds his archive.

  • I built the same game for 20 years without knowing it

    I want a world that doesn’t stop when I do.

    I didn’t know that’s what I wanted until recently. But it explains every project I’ve shipped for twenty years, and it started with a browser RPG I played in middle school during summer school.

    Lands of Hope is still live. You can play it today. Deep content, crafting queues, a community that made the world feel like it mattered. What hooked me wasn’t any single mechanic. It was the feeling that the world kept going without me. Things set in motion with consequences I had to wait for. Other people in it, doing things alongside me, making it real.

    I was thirteen. I didn’t have language for what I wanted. I just knew how it felt.

    TurboShells came first. Turtle racing where every turtle’s body assembled from its genome at render time — shell radius, leg length, color expressed from a genetic sequence. The turtles raced. The faster ones bred. The slower ones didn’t. Nobody played TurboShells. But I built it anyway, because something about setting a breeding pair in motion and waiting for the result felt right in a way I couldn’t explain.

    rpgCore next. A thousand tests. A proper ECS architecture. Genetics, lifecycle, dispatch — everything composable, everything persistent. SlimeGarden put it to work: breed slimes, dispatch them, see what comes back. OperatorGame pushed the dispatch loop into squad tactics. VoidDrift stripped it down to its core: drones go out, mine asteroid ore, return, station inventory updates, repeat.

    Every project had the same shape underneath. Something goes out without me watching. Time passes. Something comes back changed.

    The recognition came slowly. I was writing VoidDrift’s Scout dispatch system one night — drones leaving the station, doing their work autonomously, returning with ore — and I stopped. I’d written this before. Not something similar. This exact thing. The same send, wait, return, consequence that the breeding pairs were running. That the slimes were running. That my Lands of Hope crafting queues were running when I was thirteen.

    I opened a list of every project I’d shipped and read it from the top. The dispatch loop was in all of them. Not because I’d planned it. Because I kept arriving at the only mechanic that produced the feeling I was chasing.

    A world that goes on without you. That changes while you sleep. That has consequences whether you’re watching or not.

    ContentPipeline publishes while I’m at work. PrivyBot fires its morning briefing whether I’m awake or not. RALPH ran overnight tasks the first night I deployed it and had results waiting when I woke up. VoidDrift’s drones mine whether the screen is on.

    I haven’t been building games. I’ve been building persistent worlds.

    The surprise was that naming it didn’t feel like a limitation. It felt like a body of work.

    Scattered projects suddenly had a spine. TurboShells wasn’t a side project that went nowhere — it was iteration three on something I’ve been refining since middle school. rpgCore wasn’t over-engineering — it was building the foundation the loop deserved. VoidDrift isn’t just a mining idle game. It’s the clearest version yet of the thing I’ve been trying to make since I was thirteen.

    The struggle was that I couldn’t have named this pattern while I was inside it. Patterns are invisible to the person living them. You need the list, the distance, the moment when you stop mid-implementation and recognize the shape.

    AntColony is next. Same chassis as VoidDrift, same loop underneath — workers dispatching, foraging, returning, colony state updating without you. Different world. Same feeling.

    I know what I’m building now. I’m building worlds that don’t stop when I do.

    I’ve always been building that. I just needed twenty years to see it.

  • 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.