The Art of Constraint
There is a peculiar beauty in constraint. When we declared that GameEngine.js would be
"dumb"—limited to 600 lines, stripped of all game logic, reduced to the role of a mere conductor—we were
not imposing limitation. We were engineering freedom.
This essay is a meditation on that freedom: what it has bought us, what it will cost us, and why the Hub-and-Spoke architecture is not merely a technical decision, but a philosophical stance on how complex software should evolve—particularly when the contributors include both human and non-human agents, and the laws must be enforced mechanically, not culturally.
The Conductor Philosophy
Why Dumb is Smart
Imagine an orchestra. The conductor does not play the violin. The conductor does not tune the oboe. The conductor stands at the center, beats time, and ensures that seventeen independent musicians produce one coherent symphony.
Our GameEngine.js is that
conductor. At 474 lines today (comfortably under the 600-line constitutional limit), it imports 25+
systems and orchestrates their initialization and update cycles. It knows that systems exist; it doesn't
know how they work.
// What it DOES:
this.world = new WorldSystem();
this.enemies = new EnemySystem(this.world, this.scene, this.player, this.fx, this.loot);
this.camera = new CameraSystem(this.scene, this.player);
// What it DOESN'T do:
// - Collision math
// - Pathfinding algorithms
// - Shader compilation
// - Stat calculations
This is not laziness. This is intentional blindness.
The Spaghetti Prophecy
Every game engine that survives long enough becomes spaghetti. I have seen it. You add one "temporary" helper function to the main file. Then another. Then someone adds a damage calculation because "it's just easier here." Six months later, your 600-line engine is 4,000 lines, and every bug requires understanding all of them.
"Our constitution was written to prevent this future."
The Conductor Rule
GameEngine.js may only call init(), update(), and route
input. No math. No physics. No rendering logic.
Permission First
Before adding ANY code, you must ask: "Which system owns this?" If the answer is unclear, you must propose a new system or refuse.
These are not suggestions. They are hard stops. The architecture enforces a kind of humility: the engine admits it doesn't understand the game.
The 17-Spoke Reality
Today, we have seventeen system files in /js/systems/:
| System | Lines | Responsibility |
|---|---|---|
| WorldSystem.js | 422 | Grid, tiles, spatial data |
| EnemySystem.js | 355 | Enemy AI, states, logical movement |
| UIManager.js | 450+ | HUD, overlays, DOM interaction |
| LootManager.js | 220 | Pickups, collectibles |
| CombatPhysics.js | 140 | Hit detection, damage routing |
Several of these are approaching the 500-line "Folder Trigger" threshold. When WorldSystem.js crosses that line,
it will be decomposed:
This is not technical debt. This is planned evolution.
The Hybrid Asset Pipeline
The Three Lanes
We are not building one rendering system. We are building three, unified by one interface.
This is an unusual choice. Most engines pick one rendering strategy and commit. We are deliberately mixing:
SDF (Signed Distance Fields)
Pure math in the fragment shader. No mesh data. Used for our "hero" assets—the player, bosses, data cores. These assets get the premium treatment: smooth edges, animated distortion, glow effects that emerge from the geometry itself.
GLTF
Standard 3D models exported from Blender, Meshy, or any AI art tool. Used for environment pieces—platforms, walls, alien flora. These give us artistic flexibility and let us leverage external tooling.
Kitbash
JSON-defined assemblies of primitives. A pillar is cylinder + box. A crate is box + glow plane. These are fast to iterate and trivial to version control.
The Aesthetic Payoff
"The visual signature of a raymarched SDF is unmistakable."
When the player's drone rotates, its edges remain mathematically perfect at any screen resolution. When a boss unfolds, its geometry can morph continuously without mesh topology constraints. When a Data Core pulses, the glow originates from the signed distance itself—not a post-processing hack.
These are not subtle differences. They are the difference between "generic indie game" and "this studio built something unusual."
The Action Pivot
From Clicker to Zelda
Our codebase carries the DNA of an earlier game: an idle/clicker prototype where you accumulated Entropy, deployed passive "Drills," and waited for numbers to increment.
We are surgically removing that DNA.
| Legacy Pattern | Replacement |
|---|---|
| Entropy (high-magnitude currency) | Bits (picked up from defeated enemies) |
| Fragments (upgrade tokens) | Keys (discrete dungeon keys) |
drill(player) (1.5s
channel) |
DELETE. Instant proximity collection. |
buyUpgrade(id) (stat
modifiers) |
DELETE. Progression is item-based. |
| Passive "Carrot" spawns | Enemy drops + chest rewards |
This is not refactoring for cleanliness. This is a genre transplant.
The Ownership Problem
Here is where Law #3 (Transform Ownership) becomes critical.
The PlayerDrone owns its mesh transform. The FXSystem wants to apply screen shake when the player is hit. The CombatPhysics system wants to apply knockback. The EnemySystem wants to push the player on collision.
Who moves the player?
The answer: only the PlayerDrone moves the PlayerDrone. Every other system must submit requests:
// WRONG (Law #3 violation):
fxSystem.shakeCamera();
player.mesh.position.x += Math.random() * 0.1; // FX mutating player!
// RIGHT:
player.requestKnockback({
direction: { x: -1, y: 0 },
force: 0.5
});
// PlayerDrone reads this in its own update() and applies movement.
This indirection feels bureaucratic. It is. But it is the only pattern that scales. When five systems want to influence one transform, you need a single arbiter. The owner is the arbiter.
Future-Proofing
The Spaghetti Immune System
Complex AI enemies are coming. The "Logic Grid" biome envisions enemies that adapt their behavior. The
AIBrain.js module already
exists, providing a Behavior Tree backbone. But trees become forests.
Our defenses:
- • The Folder Trigger (Law #2): When EnemySystem.js crosses 500 lines, we decompose.
- • Data-Driven Enemies: Enemy definitions live in JSON, not code. Code only provides the execution engine.
- • Event Isolation: Enemies communicate via events, not direct calls. Neither system knows about the other.
The Cathedral and the Bazaar
There is a famous essay about software development: The Cathedral and the Bazaar. It contrasts two models—the carefully designed cathedral, built by a small team to a precise plan, versus the bazaar, where a thousand contributors throw code at the wall and see what sticks.
"We are building a cathedral."
Not because cathedrals are better—bazaars have produced Linux, Apache, and Wikipedia. But because our
team is small, our time is limited, and our vision is specific. We cannot afford to refactor the whole
codebase every time someone adds a helper function to GameEngine.js.
The Hub-and-Spoke architecture is our blueprint. The 600-line limit is our flying buttress. The Folder Trigger is our vaulted ceiling. Law #3 is our rose window—complex, beautiful, and absolutely non-negotiable.
We are twenty-three days into the pivot. We have seventeen spokes. We have an asset pipeline that mixes shaders and models and primitives. Our team is unconventional: the Lead Architect role is filled by an AI system (Claude Opus), while the Technical Artist/Engineer tickets are executed by agentic models (Gemini Flash) operating on the Antigravity platform. The human provides vision and approval; the machines provide velocity and precision.
And we have a conductor who knows nothing—and that is precisely why the music plays.
Quick Reference
Core Laws
Conductor Rule
GameEngine.js ≤ 600 lines, orchestration only
Folder Trigger
If file > 500 lines → decompose into subfolder
Transform Ownership
Only the owner mutates its transforms
Permission First
Ask "which system owns this?" before coding
Aesthetic Integrity
UI follows frontend-design.md
Checkpoint Rule
Branch before risky tasks, commit after