Developer Log
This section is for people curious about how CONTRABAND was made. It's a solo project built over several months using only vanilla JavaScript, CSS, and HTML — no game engine, no frameworks. The entire game runs in a browser tab.
Why no engine?
The obvious choice for a browser game would be Phaser, Three.js, or Godot HTML5 export. I chose vanilla because:
- File size. The full game is under 120KB of code. Even with assets, the browser loads it instantly. A Unity WebGL build would be 20-50MB.
- Startup speed. From URL to playable in under 2 seconds on a modern phone. No engine warmup.
- Modular architecture. Each system is its own file with a clean boundary. Combat doesn't know about stories. Stories don't know about combat. They communicate via an EventBus.
- Zero vendor lock-in. If a framework dies, I lose nothing. Web standards don't break.
Architecture
The game has five conceptual layers:
- Core — EventBus, State, Boot sequence. ~500 lines.
- Data — Pure JS data files: ships, items, crew, story, achievements. ~3,500 lines.
- Systems — Combat, Galaxy, Timeline, Market, Story engine, Crew, Monetization, Analytics, Audio. ~2,000 lines.
- UI — HUD, Modals, Toasts, panels for each system. ~1,500 lines.
- CSS — Design system + component styles + mobile responsive. ~2,500 lines.
Total: roughly 10,000 lines across 50+ files.
Key design decisions
EventBus over direct coupling
Every system emits events ('combat:victory', 'story:decision', 'galaxy:jump') instead of directly calling other systems. This means adding a new feature — like Analytics — is just "listen to existing events." Zero refactoring of the rest of the code.
State as single source of truth
There's one GameState instance. Everything reads from it. Saving is just JSON.stringify(state). Loading is JSON.parse + Object.assign. No complex reducers, no middleware.
Procedural audio
I didn't license any music. The ambient soundtrack is six overlapping sine waves with LFO modulation, generated at runtime by Web Audio API. It costs 0 bandwidth and 0 dollars.
ASCII portraits
Character portraits are ASCII art, not images. Each NPC has a small 4-line pattern rendered in the gold accent color. This was a creative choice to fit the game's retro-terminal aesthetic, but it's also practical: zero image assets, zero loading time.
What I'd do differently
If I started over, I'd:
- Ship mobile-first instead of retrofitting
- Write the story engine to handle async loading per chapter — would cut initial bundle size by 40%
- Add analytics from day one so I could see where playtesters dropped off
- Not build the achievement system until after I had 10 players, since half the achievements turned out to need tuning
Development stats
Rough numbers:
- Months from concept to v3.8: ~3
- Lines of code: ~10,000
- Narrative scenes: 63
- Hours of my life poured into this: don't ask
Built in Irving, Texas. Fueled by too much coffee.
Individual devlog posts
- Why Vanilla JavaScript — Tech stack choices and tradeoffs.
- Designing the Sacred Timeline — How the rewind mechanic evolved.
- Procedural Audio — Zero audio files, infinite runtime variation via Web Audio API.
- ASCII Portraits — Why character portraits are text.
- Stripe & Firebase Integration — Server-side entitlement architecture.
- Three.js Lessons — Browser 3D combat rendering.
- Monetization Philosophy — Free base game + paid epilogues, why.