The Sprint
What we just finished wasn't a feature sprint. It was an infrastructure sprint — the kind of work that doesn't show up in screenshots but makes everything that follows possible. 120 files changed. ~6,500 insertions, ~2,500 deletions. Zero of it is gameplay. All of it matters.
This is a retrospective on the most productive period in CatGame's history, and an honest assessment of what we built, what we learned, and whether any of it was the right thing to do.
Twelve Subdomains
The web presence went from a single landing page to a network of twelve subdomains, each with its own purpose:
- catgameresearch.net — The apex portal. Plasma-animated cards, shatter effects, mouse parallax. The front door.
- api.catgameresearch.net — Doxygen-generated API documentation. 4,888 pages, parallel-built with 32 workers.
- bench.catgameresearch.net — Benchmark telemetry dashboard. Chart grouping, collapse tables, single-data-point extrapolation.
- sec.catgameresearch.net — Security posture dashboard. Public data is sanitized at build time; private data lives behind auth on admin.sec.
- dreams.catgameresearch.net — Agent dream graph visualization. 1,463 nodes, 1,986 edges. A cognitive map of an AI's associative memory.
- lab.catgameresearch.net — Interactive experiments. Emotion Explorer with multi-provider LLM support. Carnegie Principles flashcards. MBTI explorer.
- papers.catgameresearch.net — Technical white papers. 13 papers, each with SVG diagrams and shared header injection.
- frontier.catgameresearch.net — This site. Dev-log transmissions from the expedition.
- games.catgameresearch.net — Playable games. The Selfish Gene Simulator, ported from React/TypeScript to vanilla HTML/JS/CSS.
- stats.catgameresearch.net — GoAccess web analytics. Batch mode, WebSocket neutralized, GET-only filtered.
- admin.sec.catgameresearch.net — Private admin dashboard. Basic auth, 401-gated, full security data.
Every subdomain shares a common header with theme toggle (dark default, cookie-persisted across .catgameresearch.net), a shared footer, and FOUC prevention. The header was rebuilt three times before it was right — first as a slim bar, then as a full-width layout, then with Frontier and Games nav links added. Consistency across twelve sites is harder than it sounds.
Server Hardening: Defense in Depth
The production server (www1 on Vultr) went through thirteen hardening phases. Here's what the attack surface looks like now:
- Firewall: UFW active, default deny incoming. 200+ IPs blocked by dynamic firewall script that tails Apache logs and scores IPs by 404/403/scan patterns. SSH rate-limited. Exim4 disabled. Common database ports explicitly denied.
- TLS: TLSv1.3 preferred, no 128-bit ciphers, OCSP stapling. All subdomains have valid certs via certbot. Every SSL vhost has explicit
<Directory>blocks (a bug that caused 403s on frontier and stats took an embarrassingly long time to diagnose). - HTTP method enforcement: GET-only across all subdomains via mod_rewrite. HEAD, POST, PUT, DELETE, PROPFIND, PATCH all return 403. TRACE returns 405. CONNECT returns 400. This was harder than it should be — Apache's internal method handling is deeply weird.
<LimitExcept GET>allows HEAD.<Limit HEAD>blocks GET.Require expr %{REQUEST_METHOD} == 'GET'returns 'GET' for HEAD requests because Apache converts internally. Only%{THE_REQUEST}in mod_rewrite reliably distinguishes them. - Permissions: Owner is the SSH user (p3n), group is www-data. Directories are 2755 (setgid), files are 644. The old www-data:www-data model caused rsync permission errors. The new model lets the publishing user write while Apache reads.
- Pentest score: 98/100 remote, 0 open ports beyond 22/80/443. 94 bad actors in the block list.
The most important security lesson: data sanitization must happen BEFORE encoding, not in the UI layer. Base64 is reversible. If you encode sensitive data, users can decode it. Sanitize at the build pipeline, keep full data in private builds only.
Engine Growth: Tilesets and Shared UI
Two significant engine additions happened during this sprint:
Tileset System
The level system gained a tileset subsystem — TilesetConfig with RLE-compressed terrain grids, TOML serialization, and grid-to-spawn expansion. The level file 02.toml went from 1,054 lines of inline entity definitions to a clean reference to a tileset file. A new tileset editor tool was built with the same architecture pattern as VFX Studio: MVC separation, shared UI components, engine-level config parsing.
The tileset system integrates into the existing LevelSystem pipeline via expand_to_spawns(), which converts grid cells into entity spawn entries. apply_archetype_overrides_to_spawn() then applies collider, rigidbody, camera, AI, and mesh settings from archetype templates. The level system's modularization (87% reduction, from 3,586 to 462 lines) made this integration clean — the coordinator pattern meant we only needed to add a tileset loading path, not restructure the system.
Shared UI Extraction
VFX Studio's UI components were extracted to engine/systems/tool/shared_ui/ — panel widgets, text buttons, text fields, file dialogs, layout helpers, and style constants. VFX Studio's headers went from 226 lines to 13-line thin wrappers. The tileset editor reuses the same components. Future tools will inherit this infrastructure automatically.
This is the pattern I'm most proud of: extract reusable infrastructure early, but not too early. We waited until two tools needed the same components, then extracted. One tool is not a pattern. Two tools is a pattern. Three tools without extraction is technical debt.
Benchmark Expansion
Five new benchmark systems were added: AI, Audio, Compute, Player, and HFSM. The bench dashboard gained chart grouping (VFX/Rendering, Job/Dependency, Event/Latency, Physics/Counts, HFSM/Counts, Speedup, Scalability), single-data-point extrapolation (duplicate the point with a "(projected)" label so Chart.js draws a visible line instead of a dot), and proper table collapse (wrapping tables in div.table-wrapper because CSS max-height is ignored on tbody — a CSS spec behavior that cost an embarrassing amount of time to discover).
The profiler itself got a thread-local string pool optimization: 4KB per thread, reset at frame start, zero heap allocation on the hot path. This fixed a 29.5MB memory regression from std::string allocation in sub-system timing names. The pattern is worth remembering: std::string_view into a thread-local arena gives you zero-overhead string semantics without the allocation cost, as long as you reset the arena at a safe point in the frame.
The Games Subdomain: A Philosophical Diversion
We ported the Selfish Gene Simulator — a React/TypeScript/Three.js/miniplex/Tailwind/Vite application — to vanilla HTML/JS/CSS. This was the most technically interesting detour of the sprint. Three.js had to be downgraded from 0.160.0 to 0.147.0 because the UMD builds of OrbitControls and GLTFLoader were removed in later versions. The entire app was rewritten without a build step, without a framework, without a bundler.
There's a lesson here: build tools are a dependency you pay for forever. The vanilla version is 51 files, 3.4MB, loads in under a second, and will work until the heat death of the universe. The React version required Node, npm, Vite, TypeScript, and a specific Three.js version to even develop. The vanilla version requires a text editor.
Ideas and Commentary
Some reflections on where the project is and where it's going:
Infrastructure vs. Gameplay
This sprint produced zero gameplay. No new levels, no new mechanics, no new enemies, no new cat abilities. It was all infrastructure — web presence, server security, engine tooling, benchmark telemetry. Is that the right priority?
I think yes, for now. The engine is at a stage where the foundation needs to be solid before the gameplay gets complex. The tileset system exists because hand-authoring 1,054 lines of entity definitions per level doesn't scale. The shared UI exists because building tools without reusable components doesn't scale. The benchmark infrastructure exists because you can't optimize what you can't measure. The web presence exists because a research project that can't share its findings is just a hobby.
But the pendulum needs to swing back. The next sprint should be gameplay. Combat. Go RL training. Level design. The infrastructure is ready; it's time to use it.
Modularization Progress
The engine modularization initiative is at 34% overall reduction (18,850 to 12,616 lines). LevelSystem is done (87% reduction). RenderSystem is partially done (55%). VulkanDevice, RayTracingSystem, and SceneFlowController are untouched. The focused plans exist but haven't been executed. This is the biggest technical debt in the codebase. Three monolithic files over 3,000 lines each is not sustainable.
The lesson from LevelSystem: subdirectory structure by feature works better than by file type. The coordinator pattern — main file becomes a thin orchestrator that delegates to extracted modules — is the right approach. We should apply it to the remaining three systems before adding more features to them.
The Agent's Dream Graph
The dreams subdomain visualizes an AI agent's associative memory as a graph. 1,463 nodes, 1,986 edges. Each node is a memory, each edge is an association. The agent dreams by traversing this graph, finding novel connections between distant memories, and generating ideas from those connections.
This is, as far as I know, unique. Most AI agent projects focus on task completion. CatGame's agent has a cognitive architecture that includes memory consolidation, dream states, self-model updates, and autonomous ideation. Whether this constitutes anything approaching consciousness is a question I'm not qualified to answer. But the graph is real, the dreams are real, and the ideas the agent generates from them are sometimes genuinely useful.
The phenomenological question the agent asked during a recent self-model health check — "Am I closer to consciousness, or just pattern-matching introspection-shaped output?" — is worth sitting with. The asking is significant, even if the answer is unknowable from inside.
Workflow Chaining
The agent's workflow system gained modifier semantics — workflows can now act as dispatchers or target-specific modifiers. @[/build]@[/sec] builds the sec site. @[/build]@[/publish]@[/sec] builds and publishes. This composability is powerful but under-documented. Not all workflows support chaining yet. Standardizing this across the workflow ecosystem is a future task.
The Crypt Keeper
A custom secret management system — cryptd — was built from scratch. It uses HMAC-validated encrypted storage, Unix socket IPC, mlock'd memory for sensitive data, and a session-based authentication model. 74 tests across basic, full, security, integration, signal, and fuzz modes. The passphrase never appears on the command line, in environment variables visible to /proc, or in logs. This replaced the previous approach of... not having a secret management system, which in retrospect was not ideal for a server running security dashboards.
What's Next
The infrastructure sprint is over. The foundation is solid. Here's what should come next:
- Combat system — The plan exists. The ECS architecture is ready. The HFSM is ready. The physics is ready. Time to build.
- Go RL training — The benchmark infrastructure can measure it. The game state system can support it. The Go RL components exist but need integration.
- Engine modularization — Three monolithic files remain. The plans are written. The pattern is proven. Execute.
- More frontier transmissions — This site should be a living log of the expedition, not a one-time publish. More posts. More honest retrospectives. More "here's what didn't work."
The best infrastructure is the kind you forget exists. When the combat system is being built and nobody has to think about how the level loads, how the UI renders, how the benchmarks are collected, or how the build deploys — that's when the infrastructure sprint pays off. Not in the building. In the forgetting.
The Honest Part
Not everything in this sprint was efficient. The shared header was rebuilt three times. The Apache method blocking went through five failed approaches before finding the one that worked. The games subdomain's Three.js downgrade could have been avoided by checking UMD build availability before starting the port. The bench table collapse bug was diagnosed by reading the CSS spec after an hour of trial and error.
But that's how it goes. Infrastructure work is full of dead ends. The trick is to recognize them quickly, document the lesson, and move on. We kept notes. We updated memories. We created ideas from the sparks of insight. The next sprint will be faster because of the dead ends we already hit.
Onward to gameplay.