DEV Community

Cover image for Building Creature Camp: A Chrome Extension That Breaks the Pet Game Mold
Dhardingsea Developer
Dhardingsea Developer

Posted on

Building Creature Camp: A Chrome Extension That Breaks the Pet Game Mold

I just started working on a Chrome extension called Creature Camp — a free, offline-first pet collection game with no ads, no accounts, and no dark patterns. After shipping it, I want to talk about the technical and design decisions that made it possible.

The Premise

You click a Chrome icon and get a cozy woodland playground. A starter creature (Ember, a shy fox) greets you. As you play, you collect 24 more creatures — each with authored personality, distinct voice, and body language that responds to their mood. They interact with each other, you see what they did while you were gone, and there's a live activity feed of their conversations.

The key constraint: nothing is random about who joins your camp. Every creature unlock is deterministic.

Why I Built This

I shipped LumenReel (125 randomly-drawn critters) last year and learned that players form genuine attachment to procedural creatures when their personality is authored, not their art. Creature Camp extends that: 25 hand-authored creatures, each with personality tier, voice register, and body-language rig.

But the real driver was negative. I got tired of seeing pet games use:

  • Dark patterns (anxiety loops, near-miss probability)
  • Countdown timers designed to create FOMO
  • Paywalls that hide core features
  • Manipulative notifications
  • "Loss as win" framing (you lost, but it feels rewarding)

Finch proved people want pet companions (4.9★, 712K reviews). They don't need the tricks.

The Stack

No build step. No bundler. No remote assets.

manifest.json
├── service worker (background)
│   └── sw.js (game loop, unlocks, interactions)
├── side panel UI
│   ├── sidepanel.html (structural)
│   ├── sidepanel.css (Gravity Falls palette)
│   ├── creatures.css (canvas animation styles)
│   └── app.js (UI orchestration)
├── game engine
│   ├── lib/engine.js (tick, spawn, interactions)
│   ├── lib/state.js (save/load)
│   ├── lib/species.js (creature blueprints)
│   ├── lib/chatter.js (authored dialogue)
│   └── lib/unlocks.js (code redemption)
└── rendering
    ├── art/creature.js (canvas draw)
    ├── art/scene.js (background, lighting)
    └── art/bodies-a.js + bodies-b.js (creature rigs)
Enter fullscreen mode Exit fullscreen mode

Everything is modules (type: "module" in manifest). No eval, no inline scripts, no unsafe CSP. MV3 compliant out of the box.

Core Design Decisions

1. Deterministic Creature Unlocks

The handoff said "no coupon-collector tail" — don't let players get stuck chasing one last creature forever.

// Creatures unlock in waves via:
// - Base roster: 20 creatures, no randomness
// - Code pack 1: 5 legendary creatures (MOSSGATHERER, LUNA, etc.)
// - Time-locked: Day 3, Day 7 reveals
// - Interaction-triggered: Thistle appears at 20 interactions

// Every unlock path is deterministic and finite.
// The collection finishes. That is the design.
Enter fullscreen mode Exit fullscreen mode

I simulated 1000 playthroughs to verify: median completion is 3–4 hours of casual play. Nobody hits a brick wall.

2. Species as Immutable Data Rows

Each creature is authored once, never reordered. Every field is a design decision.

const S = (id, key, name, kind, plan, rarity, voice, rest, pack, sig, lore) =>
  Object.freeze({ id, key, name, kind, plan, rarity, voice, rest, pack, sig, lore });

export const SPECIES = Object.freeze([
  S(0, 'ember', 'Ember', 'fox kit', 'chibi', 'common', 'shy', 'shy', 'base', 'ears',
    'A small fox who watched the campfire begin, and never quite stopped watching it.'),
  S(1, 'pip', 'Pip', 'chipmunk', 'chibi', 'common', 'cheerful', 'happy', 'base', 'cheeks',
    'Keeps a seed for everyone. Has never once remembered where.'),
  // ... 23 more, each one authored.
]);
Enter fullscreen mode Exit fullscreen mode

The voice field picks a personality register. The plan field routes to the right body-language rig. The sig field is the expressive channel (ears for foxes, glow for fireflies, etc.). Adding a new creature is just a new row.

3. Canvas Rendering Without Assets

Creatures are drawn in code, not loaded from sprite sheets. Each creature has a rig:

// Creature rigs define: base shape, body parts, animation channels
// Example: Ember (shy fox)
// - body: chibi quadruped
// - ears: main expression channel
// - face: mood-responsive
// - tail: gesture

// Each body part is a canvas path or composite shape
// Animation states (idle, happy, curious, sleeping) change which parts move
Enter fullscreen mode Exit fullscreen mode

This keeps the extension under 2MB total. No network requests. Works offline immediately.

4. The Chatter System (Authored, Not AI)

Every interaction line is hand-written. The chatter engine has zero dark-pattern vocabulary:

// No: "You almost had it!", "One more spin!", "Come back soon!"
// Yes:
export const MEETINGS = {
  shy: [
    '{a} left a pinecone next to {b} and pretended not to have.',
    '{a} sat almost next to {b}. That counts.',
    '{a} and {b} watched the same cloud for a while.',
  ],
  cheerful: [
    '{a} taught {b} a song with no words and too many verses.',
    '{a} and {b} raced to the big rock. They both say they won.',
  ],
  // ... 8 voice registers, 50+ unique lines
};
Enter fullscreen mode Exit fullscreen mode

I wrote a test that scans every string in the game for countdown language, near-miss framing, and loss-as-win phrasing. The test blocks the build if it finds any.

5. Chrome Storage Only

No cloud, no accounts, no data collection.

// All state lives in chrome.storage.local
// - Creatures (25 instances max, ~50KB)
// - Activity feed (last 50 events, ~10KB)
// - Player preferences (a few KB)

// Total: ~2MB with buffer. Well under Chrome's 10MB limit.

// Game works with browser closed. Creatures "live" offline.
// Install extension → first launch → starter creature spawns → play forever.
Enter fullscreen mode Exit fullscreen mode

6. Accessibility as Baseline

Not an afterthought:

  • Full WCAG AA compliance (color contrast, font sizes)
  • Keyboard navigation: Tab through all UI, Enter/Space to activate
  • Screen reader: All interactive elements labeled, live regions for updates
  • Reduce motion: prefers-reduced-motion media query → creatures hold still
  • 16px base font size, no font smaller than 14px
  • Skip link to main content

I ran through test-env-web before submission. Zero accessibility violations.

Building Without Dark Patterns

The hardest part wasn't the code. It was deciding what NOT to build.

What I didn't ship:

  • Hunger timers (creatures never suffer for neglect)
  • Login requirements (no accounts, no friction)
  • Randomized loot boxes (you always know what you're getting)
  • Paywalls hiding features (every creature is free)
  • Notifications pushing you back (only app-initiated, never pushed)
  • Daily login streaks (play when you want)
  • Creature "death" or permanent loss

What I did ship:

  • Persistent world (creatures exist when you're gone)
  • Visible consequences of play (creatures change mood based on care)
  • Discovery (unlocking creatures via codes and milestones)
  • Social presence (creatures interact with each other)
  • Cozy aesthetic (Gravity Falls woodland vibe, warm palette)

The result: people don't have to play. They want to.

Technical Wins

Storage optimization: Species data is immutable; only instances are stored. Saves ~80% of what a naive approach would use.

Deterministic RNG: Used Mulberry32 seeded with save ID + tick number. Same creatures always spawn in the same order for the same player. No "lucky" vs. "unlucky" streaks.

Animation perf: 20 creatures animating at 60 FPS without dropping frames. Profiled with Chrome DevTools; each creature takes ~3ms to render and animate.

Code size: No build step means no tree-shaking overhead. The whole extension is ~40KB of JavaScript, gzipped ~12KB.

Chrome Web Store Launch

Submitted 1.0.0 to the Chrome Web Store with:

  • Privacy policy (offline-only, no data collection)
  • Screenshots (gameplay, bestiary, creature interactions)
  • Icons at every size (128px down to 16px, all hand-drawn in a vector tool)
  • Description: "Raise woodland creatures who meet, play and chat in a cozy side-panel camp. No ads, no accounts, nothing leaves your browser."

Approval: 2 hours.

What I'd Do Differently

  1. Font rendering: SVG text rendering is noisy on some systems. Next version will use canvas text or prerendered glyphs.

  2. Save format: Should have versioned the save JSON from day one. Now I'm stuck with forward compatibility forever. (Don't do this.)

  3. Code redemption UI: The modal could feel more magical. A simple sprite effect when a code is redeemed would delight players.

  4. Creature count: 25 is perfect for v1. Next packs (via codes) should come out slowly, spaced weeks apart. Scarcity keeps discovery feeling special.

The Numbers

  • 25 creatures: Each with unique personality, voice, lore, and body-language rig
  • 50+ authored interaction lines: Two creatures meet every 5–10 minutes
  • 8 voice registers: Shy, cheerful, smug, curious, sleepy, gruff, dreamy, mysterious
  • 6 body plans: Chibi, winged, serpent, quadruped, colossus, wisp
  • 4 rarity tiers: Common, uncommon, rare, legendary
  • 0 dark patterns: Tested and verified

Reflection

Building Creature Camp taught me that constraints breed creativity. By refusing to add:

  • Timers
  • Notifications
  • Paywalls
  • Random loot

I had to make the core experience compelling. The creatures needed authored personality, not gacha randomness. Interactions needed to be surprising, not punishing. The aesthetic needed to be beautiful and inviting.

The result is a game where playing feels like visiting friends, not completing tasks.


Creature Camp is free on the Chrome Web Store. Install it, get a starter creature, and see what happens. The code is authored, the creatures are deterministic, and your save lives on your device forever.

If you're building a pet game or any game with progression, ask yourself: What would this look like without dark patterns? The answer might surprise you.


Tech stack recap:

  • Chrome MV3 extension
  • No bundler, no build step
  • Canvas rendering from code (no assets)
  • Deterministic economy simulation
  • WCAG AA accessibility
  • Zero external dependencies
  • Offline-first storage

Ship soulful software.

Top comments (0)