I’ve been building a small project called cart over at yetric/cart. The original idea was simple: build a game engine that behaves more like a console than a framework.
That means fixed rules, explicit capabilities, and a cartridge model instead of an everything-goes architecture. Games are cartridges. The machine defines the limits. If the hardware doesn’t expose something, the game doesn’t get it by accident.
What I like about the project now is that it has moved past the “nice idea” stage. There is enough real tooling and runtime behavior around it that the concept is starting to answer back.
It started with scenes, not abstractions
A lot of engine experiments die because they stay abstract for too long.
cart only got interesting once it had real scenes to build and real content to manage. Early on that meant a cabin scene, tile maps, collision, and enough runtime structure to move a character around something more concrete than a blank screen.
One of the first genuinely useful tools I added was an in-browser collision editor for the cabin scene. That was not glamorous work, but it forced the project to stop pretending that map data would just sort itself out.
As soon as you have to edit collision data visually, a bunch of things become obvious very quickly:
- your scene format matters
- your runtime assumptions matter
- world-space vs screen-space matters
- authoring workflow matters more than you thought
That was the first point where cart started becoming a system instead of a rendering demo.
Then it turned into a real scene editor
From there I pushed much further and built a proper tile editor.
Not a toy panel with a couple of buttons. An actual in-browser editor with paint, fill, and pick tools, four map layers plus collision, visibility toggles, undo/redo, tile preview, drag-selection in the tileset picker, import/export, variable map sizes, and load/save support.
I also added AI-assisted stage generation, which is fun, but the more important part is that there is now a real authoring surface for the engine.
That matters because engines without tools tend to lie about how usable they are. You can get surprisingly far hardcoding arrays in TypeScript. Then the first time you want to adjust a room layout, tweak collision boundaries, or build a larger map, everything gets miserable.
Once the editor existed, the project had to deal with proper map data, collision overlays, multi-layer scenes, different cartridge shapes, and the boring details that distinguish “could work” from “is workable.”
The runtime had to grow up too
The editor only matters if the runtime can support the kinds of scenes you want to author.
So a lot of the work in cart has been runtime work:
- tile-based collision instead of a looser pixel-rect approach
- camera support and world-space map layers
- HUD rendering in screen space
- font loading and text rendering
- audio with music and sound effects
- state handling for pause, win, lose, and transitions
The camera/world-space work was one of the bigger turning points. Once the runtime could handle maps larger than the viewport, scrolling, and world-aware layer rendering, the project stopped feeling like “single-room demo engine” and started feeling like something you could actually build small games in.
Audio was another step like that. Music, SFX, simple synth-generated sounds, scene transitions, crossfades — none of it is groundbreaking, but it changes the feel of the project immediately. A runtime with movement, scenes, HUD, and audio starts to feel like a machine instead of a rendering experiment.
I also built the cartridge model out properly
Another thread of work was making the cartridge concept less hand-wavy.
At one point I added manifest-driven codegen for typed asset bindings, then a small cart CLI with commands like validate, codegen, inspect, build, and init. That was the beginning of making the machine visible instead of just implied.
I like that direction a lot.
If the whole pitch is “the game declares its hardware and the engine enforces it,” then there should be tooling around that:
- validate what a cartridge declares
- inspect what it consumes
- build it in a repeatable way
- scaffold new cartridges without guesswork
That kind of tooling makes the constraint model real. Otherwise “console-like” is just branding.
The cartridges became better tests of the engine
The project got a lot healthier once there were multiple cartridges pulling on the engine from different directions.
The adventure cartridge pushed scene transitions, combat, enemies, and top-down game state.
The platformer pushed scrolling maps, parallax backgrounds, drop-through platforms, respawn flow, and pause behavior.
The puzzle cartridge pushed a different kind of state model entirely: swaps, matches, flashes, win conditions, no-move reshuffles, and title/win presentation.
That is when the engine starts telling you the truth.
It is easy to make an architecture look good when every example is basically the same game wearing a different hat. It is much harder when one cartridge wants camera movement, another wants combat timing, another wants board-state logic, and they all share the same machine rules.
That tension is useful. It is where the real design pressure comes from.
The latest work made it feel like an actual console
The most recent round of work is what made everything click together.
First, I refactored the project so each cartridge owns its own assets and manifest. Sprites, tilesets, sounds, entry point — all co-located under src/cartridges/<game>/. The runtime and editor read cartridge manifests directly. No awkward shared asset layer pretending not to be global state.
Second, I replaced the old HTML launcher with an in-engine selector cartridge. Open the project and you land inside the machine. D-pad through cartridges. Press A or Start to launch one. That sounds like a small UX change, but it matters a lot. Choosing a cartridge now happens inside the world the project claims to be building.
Third, I added a runtime-level “hold SELECT for one second to return to the selector” behavior. That belongs in the machine, not in individual cartridges, and it makes the whole test loop much smoother.
That trio of changes did more than clean up the code. It made the abstractions line up with the experience.
Why I keep working on this
Modern engines mostly optimize for flexibility. cart is trying to do almost the opposite.
Fixed resolution. Fixed tick rate. Explicit assets. Explicit manifests. Harder boundaries. Fewer magical escape hatches.
That is partly aesthetic, sure. But it is also practical. Constraints make systems easier to reason about. They force decisions earlier. They reduce the amount of accidental complexity that hides inside “we can always support that later.”
And, honestly, it is just fun.
There is something satisfying about building a fake little console with real rules, then adding just enough tooling and game complexity to find out whether those rules are actually any good.
What’s next
The obvious next steps are more cartridges, better build/package flow, and more authoring tools.
But the real goal is simpler than that: keep pushing until the structure breaks somewhere important.
If the cartridge model survives more games, more runtime features, and better tooling, then it is probably onto something. If it doesn’t, then I will at least have learned where the idea stops being honest.
That is what I want from side projects anyway. Not just a finished artifact, but a system you can push hard enough that it starts arguing back.