BRAINROTTERS
A Story of Human + AI Collaboration

Last updated

Building Brainrotters
with Claude

How one family website went from a blank page to a full-blown multi-page world — with a driving game, a redesigned poker table, AI-powered hints, a family message board, and a full account system — all built through conversation.

📅 March 2026 🤖 Claude Sonnet 4.6 🌐 brainrotters.com 💬 Many long conversations
index .html K 🎰 E 🌙 C ⚡ Gramma 🔥 📝 M/P 🚗 driving Lurkers ⚔️🧟 brainrotters.com — site map
Site History
Every notable change, logged by session.
View Changelog →
Chapters
01 The Beginning 02 The M/P Driving Game 03 Gramma's Message Board 04 K's Poker Game 05 The Poker Makeover 06 Teaching the Game to Coach 07 Rate Limits & Real Friction 08 Accounts & the Admin 09 Upgrading the Driving Game 10 What I Learned 11 The Full Site 12 The Lurkers Game 13 The Codex & the Art Pipeline 14 Claude Code — A New Way In
01
The Beginning
↑ Top
Origin Story

A Family Website Called "Brainrotters"

It started with an idea: build a fun, personal website for a family — with a page for each member. The name? Brainrotters. The vibe? Dark backgrounds, neon glows, and a sense that someone genuinely designed this for their actual family, not a template.

The site was structured around six subpages — K, E, C, Gramma, M/P, and a secret page for John — each with its own color theme and personality. And it would live at brainrotters.com, uploaded via WinSCP and served by Hostway hosting.

Brainrotters K E C Gramma M/P Brainrotters Dark neon aesthetic — individual pages per family member
⚡ The Stack — Pure HTML, No Frameworks ⚡
The Stack

Pure HTML — No Frameworks, No Build Step

Every single page is a standalone .html file. No React, no bundler, no dependencies beyond Google Fonts. The whole site ships as six HTML files, a couple PHP files for the backend, and whatever images the family wants to upload. Drag-and-drop via WinSCP to brainrotters.com.

The server ran PHP 4.4.9 (from 2008!) until a support call to Hostway got it upgraded to PHP 8. That upgrade unlocked the leaderboard and message board features.

02
The M/P Driving Game
↑ Top
First Big Feature

"M Loves Her Family!" — A Canvas Driving Game

The M/P page wasn't just a profile — it became a full browser game. The site owner drives a white Tesla around a little neighborhood map, picking up K, C, and E from their homes and delivering them to destinations (schools, shops, Spectrum, the lab). 120-second timer. 100 points per delivery.

🏫 K's School 🏠 Home 🔬 Lab SCORE 300 TIME 0:23 👧 👦
🕹️

Virtual Joystick

Started as tap-and-hold steering, evolved through D-pad buttons, ended up as a drag joystick zone below the canvas.

🏎️

Road Speed Boost

When the Tesla is on a road, max speed doubles. Driving on the grass? Slow going.

🏆

Leaderboard

Top 5 high scores with 🥇🥈🥉 medals. Enter a 3-letter name. Saved server-side via PHP to leaderboard.json.

📦

PHP Upgrade

The server was running PHP 4.4.9 (from 2008!). A call to Hostway got it upgraded to PHP 8 to enable the backend.

03
Gramma's Message Board
↑ Top
Family Connection Feature

A Living Bulletin Board — Password-Protected

Gramma's page became something genuinely touching: a message board where each family member (K, C, E, M/P, and Gramma herself) has a color-coded card to leave a message. Each card is editable with a password. Messages reset every Monday morning automatically.

Gramma's Page Leave a message here. K Hi Gramma! Miss you 💕 New ✏️ C C, put a message to Gramma here New ✏️ E See you soon! New ✏️ M/P Love you Mom! New ✏️ Gramma 🔥 I love you all so much! Can't wait to see you ❤️ New ✏️
⚡ Technical Details — How the Message Backend Works ⚡
Technical Details

How the Backend Works

messages.php handles GET (return all messages as JSON) and POST (save a message with password auth). Passwords live in gramma_passwords.php — a simple array editable directly on the server. All five family members share the initial password "terra mia".

Auto-reset on Monday mornings works by checking the ISO year+week number against a stored value in messages_last_reset.txt. First visit each Monday wipes messages.json clean.

04
K's Poker Game
↑ Top
The Ambitious Feature

Full Texas Hold'em Against the Family

K's page got the most ambitious feature of all: a fully working Texas Hold'em poker game — complete with hand evaluation, sequential AI turns, betting rounds, pot management, and a showdown. The opponents? E 🌙 (cautious), C ⚡ (aggressive), and Gramma 🔥 (balanced).

Pre-flop Pot: 30 Your turn — check or raise? E 🌙 Chips: 990 C ⚡ Chips: 980 Raises to 20 Gramma 🔥 Chips: 1000 COMMUNITY K ♥ A ♠ 7 ♦ ⭐ K Chips: 980 Q ♥ J ♠ Two Pair Fold Check Raise 50

"The hardest bug to fix was that all the AI opponents were taking their turns at the same time — the whole flop would drop at once instead of proper sequential betting."

— The sequential turn queue problem, solved with a nextTurn() function and _actedThisStreet flags
🃏

Pip Card Rendering

Every card (2–10) draws its suit symbols in the exact real-world positions using PIP_LAYOUTS coordinates.

👸

Emoji Face Cards

J/Q/K got unique emojis per suit — 🥷🤴🎩🧙 for Jacks, 🧝👸🧜🧟 for Queens, 🧛👑🔮👺 for Kings. No external images needed.

🧠

Hand Evaluation

Full Royal Flush → High Card detection. Tests all C(7,5)=21 five-card combinations from each player's 7 available cards.

🤖

AI Personalities

E is cautious, C is aggressive, Gramma is balanced. Each reads hand strength vs pot odds to decide fold/call/raise.

⚡ Code — The Sequential Turn Queue Fix ⚡
// The sequential turn queue — fixed the "all AIs act at once" bug function nextTurn(startIdx) { const alive = players.filter(p => !p.folded); if (alive.length === 1) { endRound(); return; } for (let offset = 0; offset < 4; offset++) { const i = (startIdx + offset) % 4; if (players[i].folded) continue; if (players[i]._a && players[i].bet >= currentBet) continue; // This player still needs to act if (players[i].isAI) setTimeout(() => aiAct(i), 700); else { playerTurn = true; setButtons(true); } return; } setTimeout(advancePhase, 500); // All done — deal next street }
05
The Poker Makeover
↑ Top
Visual Redesign

From Arcade to Casino — A Complete Aesthetic Overhaul

The original poker game worked, but it looked like a coding exercise. In a new conversation, the whole visual language was rethought from scratch: a dark green felt table with a gold border glow, a warm cream color for cards, Cinzel serif fonts for the table title, and a proper "K's Table — Hold'em" header with the game tucked inside a rounded casino-style container.

Cards got a complete redesign. Number cards now use PIP_LAYOUTS — a coordinate grid that places suit symbols in the exact same positions as real playing cards (2s at top and bottom, 3s in a column, 7s with the classic offset arrangement). Face cards (J, Q, K) each got unique emoji per suit — 🥷🤴🎩🧙 for Jacks, 🧝👸🧜🧟 for Queens, 🧛👑🔮👺 for Kings.

K'S TABLE HOLD'EM 7 ♥ ♥ ♥ ♥ ♥ ♥ ♥ K 👑 COMMUNITY CARDS A ♠ Q ♦ 2 ♥ RAISE, strong flush draw — semi-bluff with 9 outs. 💡 K's Table — casino aesthetic with AI hints
🃏

Pip Card Redesign

PIP_LAYOUTS places suit symbols at precise coordinates matching real playing cards — the 7 of Hearts looks exactly like a real 7 of Hearts.

🧛

Emoji Face Cards

Each face card has a unique emoji per suit. The King of Spades is 👑, the Jack of Clubs is 🎩. No external images needed.

🔍

Card Zoom on Tap

Tapping any face-up card shows a large zoomed overlay. On a phone screen, the pip positions are tiny — this solved the readability problem.

🏆

Win Screen

At showdown, opponent cards are revealed face-up. The winning hand name (e.g. "Flush", "Two Pair") appears in gold. A proper casino moment.

🎰

Dealer Button

A gold "D" chip badge sits on the bottom border of whoever holds the button — rotating each hand, just like a real poker table.

📊

Hand Strength Labels

Top pair, middle pair, bottom pair, underpair — each gets a distinct strength label so the AI hint knows exactly how vulnerable the hand is.

06
Teaching the Game to Coach
↑ Top
AI-Powered Hints

Using a Free AI API to Give Real Poker Advice

The idea: a 💡 Hint button that sends the current hand — hole cards, community cards, position, pot size, betting history — to a language model and gets back a one-sentence coaching tip like "RAISE, strong flush draw in middle position — semi-bluff with 9 outs."

This used the Groq API (free tier) with Meta's llama-3.3-70b-versatile model. The challenge wasn't connecting to the API — it was getting the hints to be consistently correct. A dedicated trainer.html tool was built to run batches of 15 test scenarios and evaluate hint quality. Getting from first attempt to 15/15 accuracy took many rounds of prompt iteration.

"The 8-billion parameter model got 7 out of 15 wrong. It hallucinated cards that weren't in the hand, raised when it should fold, and gave hints in ALL CAPS with line breaks. Switching back to the 70-billion model fixed everything — at the cost of using 5× more of the daily token limit."

— The model size tradeoff, discovered by running a batch comparison
⚡ Prompt Engineering — 72% Token Compression ⚡
Prompt Engineering

From 408 Tokens to 116 — A 72% Compression

The original prompt was 306 words and used ~408 tokens per request. On the free tier (100,000 tokens/day), that meant only ~166 hints per day before hitting the limit. By stripping the prompt to its essential rules — 87 words, ~116 tokens — the daily capacity jumped to ~860 hints with room to spare.

The key insight was that the model doesn't need natural language explanations — it needs precise, unambiguous rules with no room for creative interpretation. Shorter and stricter outperformed longer and polite.

// Original prompt: 306 words, ~408 tokens // Compressed prompt: 87 words, ~116 tokens (-72%) const prompt = `Texas Hold'em coach. Rules: only mention draws if hole cards support them. Draws with no bet: late/middle pos → RAISE, early → CHECK. Draw facing a bet → CALL (or FOLD if weak). Never RAISE without a draw or made hand. Made hand (pair+) → RAISE for value. Underpair → CHECK. Reply: 15-25 words — action word plus brief reason. Start with action in ALL CAPS followed by comma.`
Poker Term Tooltips

A 33-Term Dictionary Built Into the Hints

Hints use real poker terminology — semi-bluff, flush draw, pot odds, c-bet, gutshot, broadway. For someone learning the game, these terms are opaque. A 33-term dictionary was added: any poker term in a hint gets a gold dashed underline, and tapping it shows a popup definition.

The tooltip positions itself relative to the tapped element — appearing just below the word in the hint text, or just below the dealer button chip if you tap that. The popup uses the same gold aesthetic as the rest of the table.

K'S TABLE J ♥ 9 ♣ 2 ♦ Q ♠ K ♥ Q ♦ 💡 RAISE, you have a strong flush draw — consider a semi-bluff with 9 outs. SEMI-BLUFF Betting or raising with a hand that isn't best yet, but has drawing potential. Tapping any highlighted poker term shows its definition
07
Rate Limits & Real Friction
↑ Top
A Very Real Problem

Hitting the API Token Limit Mid-Session

After just two hints, the daily token limit was exhausted. The error message from Groq: "Used 99,645, Requested 484. Please try again in 1m 51s." The hint button would go grey and never come back. This needed a real fix.

The solution was a live countdown timer: parse the "try again in Xm Ys" from the error message, show "Daily token limit reached — try again in 1m 51s" with a ticking clock, and automatically re-enable the button when it hits zero. The trainer got similar treatment — distinguishing between per-minute rate limits (retry with backoff) and daily limits (stop immediately, show countdown, abort remaining scenarios).

⏱️

Live Countdown

Parses "try again in Xm Ys" from the API error. Ticks down every second. Button re-enables automatically at zero.

🔁

RPM vs TPD

Requests-per-minute errors retry with exponential backoff. Tokens-per-day errors stop immediately and show the countdown — no point retrying a daily limit.

⚡

20s Fetch Timeout

An AbortController kills any request hanging longer than 20 seconds, with a clear message pointing to the API console.

📉

Token Frugality

After compression, each hint costs ~116 tokens. The daily limit now supports a full family game session with room to spare.

The model size lesson. When the 70B model kept hitting daily limits, an experiment switched to the smaller 8B model. It was faster and cheaper — but produced 47% wrong answers. Wrong actions, hallucinated draws, contradictory justifications, ALL CAPS formatting violations. The 70B model at 116 tokens/request is strictly better than 8B at any token cost.
08
Accounts & the Admin
↑ Top
⚡ Technical Details — PHP + SQLite + bcrypt Auth System ⚡
A Real Login System

PHP + SQLite + bcrypt — No Framework Needed

Adding user accounts to a PHP/FTP site means building everything from scratch. Three files handle the whole system: auth.php (server-side register/login/logout/session check, passwords hashed with bcrypt, accounts stored in a SQLite database), auth.js (a modal login/register UI injected into every page via a single script tag), and admin.php (a password-protected dashboard for the site owner).

Sessions are managed server-side via PHP's native session handling — log in once and the site remembers you as you navigate between pages. The login modal appears as a gold-bordered overlay in the same aesthetic as the poker table.

🔐

bcrypt Passwords

Passwords are hashed with PHP's PASSWORD_BCRYPT — never stored in plain text. SQLite keeps the database as a single file on the server.

📊

Admin Dashboard

Shows total accounts, logins this week, total logins ever. Per-user table with join date, last login, weekly activity bar chart, and Active/Inactive badge.

🔗

Gramma Page Integration

Each family member can link their account to their message slot. When linked and logged in, the New button skips the password prompt entirely.

👁

The Eyeball Problem

Adding a show/hide toggle to the password field required three iterations — the SVG eye icon kept being invisible against the dark background until moved outside the input field.

🌐

One Script Tag

Adding login to any page takes two lines: a script tag in the head, and a span where the link should appear. Auth state propagates automatically on every page load.

⚙️

Admin Auto-Access

If the admin account is already logged in via the regular site login, visiting admin.php skips the second login screen — no double authentication needed.

// The full auth flow — login, session, admin check // Server: PHP session + SQLite // Client: fetch + modal + renderLink() async function renderLink() { const res = await fetch('auth.php', { method: 'POST', body: '{"action":"check"}' }); const data = await res.json(); if (data.ok) { // Logged in — show username, logout, optional admin link showUserMenu(data.username); } else { showLoginLink(); } }
09
Upgrading the Driving Game
↑ Top
New Controls & Powerups

Click-to-Drive, Smarter Detection, and Boba Stops

The M/P driving game got a batch of desktop-focused improvements. First: a new click-and-hold control scheme — hold the mouse anywhere on the map and the car smoothly steers toward the cursor, continuously updating as it moves. Arrow keys still work and take priority the instant any arrow key is pressed, so both control styles can be mixed naturally in the same session.

A subtle bug had to be fixed first: the mobile/desktop detection used navigator.maxTouchPoints > 0, which returns true on desktop Chrome (it bakes in touchscreen laptop support). Switching to a user-agent string check fixed it — real mobile devices (Android/iPhone/iPad) get the joystick; everything else gets click-to-drive.

Powerup System

🧋 Boba Stops — +3 Seconds Per Pickup

After each successful delivery, a glowing 🧋 Boba Stop spawns on the map. Drive over it to add 3 seconds to the timer (capped at 30). Missed ones accumulate — every delivery spawns a new one, and uncollected stops stay on the map until picked up.

Placement is constrained: each new boba stop must be at least two road-widths from every location box and every other existing boba stop. The spawn function builds all valid road-center candidates, filters them, and picks randomly from what remains. If the map is too crowded for a valid placement, the spawn skips silently.

🏠 Home 🧋 🧋 🧋 +3s 🧋 TIME 0:18 Boba stops accumulate — pick them up for +3 seconds each
🖱️

Click-to-Drive

Hold the mouse anywhere on the map. The car steers toward the cursor smoothly, updating continuously as it moves.

⌨️

Arrow Keys Trump

The instant any arrow key is pressed, keyboard mode takes over. Release all keys and click-to-drive resumes seamlessly.

🧋

Boba Stops

Each delivery spawns a new boba stop on the map. Uncollected ones stay and accumulate. Each pickup adds 3 seconds, capped at 30.

📍

Smart Placement

New boba stops spawn at least 2 road-widths from every location and every other boba stop. No clustering.

🏆

Leaderboard Autofill

If logged into the site when hitting a high score, the name entry field pre-fills with the first 3 letters of the account username.

📱

Mobile Detection Fix

Chrome's maxTouchPoints > 0 returns true even on desktops. Switched to user-agent detection so the joystick only shows on real mobile devices.

10
What I Learned
↑ Top
Reflection

How Building with AI Actually Feels

This wasn't "AI writes code while the human watches." It was a genuine back-and-forth — the site owner would describe what was wanted, Claude would build it, the site owner would use it and notice something off ("the cards animate on hover and they shouldn't"), and the two would fix it together. Over and over, in real-time.

Some things took unexpected turns: the PHP server turned out to be from 2008 and needed a support call to upgrade. Claude's file system had persistent I/O errors during one session. The Wikimedia card images blocked hotlinking. Every real project has this kind of friction — AI collaborator or not.

Started with the structure

Six HTML pages, neon dark aesthetic, daily rotating quotes with mismatched Unsplash images (zombie quotes get K-pop images, etc.)

Built the driving game

Canvas-based, iterated through three different control systems before landing on the virtual joystick. Added road speed boost, leaderboard, 30-second timer.

Tackled the PHP server

Discovered it was running PHP 4.4.9 from 2008. Rewrote the leaderboard backend twice — once for old PHP, once after the upgrade to PHP 8.

Added Gramma's message board

Five color-coded cards, password protection, 500-char limit, Monday auto-reset. A genuinely useful family feature.

Built the poker game

The most complex feature. Debugged the sequential turn queue, redesigned cards three times (simple → pip layout → emoji face cards), fixed hover animations, removed corner suit symbols.

Redesigned the poker aesthetic

Full casino visual overhaul: dark felt table, gold border glow, Cinzel fonts, proper pip card layouts, emoji face cards, card zoom on tap, dealer button badge, win screen with opponent reveal.

Added AI hints with Groq

Built a hint system using a free AI API. Iterated the prompt engineering until hitting 15/15 accuracy. Compressed the prompt 72% to stretch the daily token limit. Added a 33-term poker dictionary with tooltips.

Handled rate limits gracefully

Live countdown timers, RPM vs TPD error separation, AbortController timeouts. The trainer tool revealed the 8B model had a 47% error rate — staying with 70B was the right call.

Built a full account system

PHP + SQLite + bcrypt from scratch. Login modal on every page. Admin dashboard with telemetry. Gramma's page linked to accounts — no password prompt when you're already logged in.

Upgraded the driving game

Click-to-hold controls for desktop, mobile detection bug fixed, arrow key priority, leaderboard username autofill, and a boba stop powerup system with smart placement logic.

Kept iterating

Every "one small thing" — a tooltip position, an eyeball icon, a boba stop that respawns in the right place — got its own conversation turn. That's just how software gets good.

The outputs filesystem broke. During the final poker game polish session, the file system mount had persistent I/O errors and couldn't write any files for the entire session. The code was correct and ready — but couldn't be delivered. That's a very real AI limitation to know about: infrastructure problems affect AI tools just like any other tool.

"The best thing about building with AI isn't that it's faster. It's that the human collaborator can try things they wouldn't have bothered trying alone — because the cost of 'let's just see what it looks like' is nearly zero."

11
The Full Site
↑ Top

index.html

Daily rotating quotes (15 total) with deliberately mismatched images. Dark neon landing page with links to all family members.

k.html

Full Texas Hold'em poker game. Casino aesthetic, pip cards, emoji face cards, AI hints via Groq, 33-term tooltip dictionary, dealer button, win screen with opponent reveal.

e.html

E's personal page with yellow (#ffe66d) theme. Back link in matching color.

c.html

C's personal page with teal (#4ecdc4) theme.

gramma.html

Purple theme. Five-card message board. Password-protected editing. Auto-resets Monday mornings. Account linking skips the password prompt if you're logged in.

mp.html

Canvas driving game. Click-to-drive on desktop, virtual joystick on mobile. Pick up K, C, E, deliver them to destinations. Boba stop powerups. PHP leaderboard with username autofill. 30-second timer.

⚙️

PHP Backend

leaderboard.php + messages.php + gramma_passwords.php + auth.php + admin.php. Server-side JSON and SQLite storage. Upgraded from PHP 4 to PHP 8.

🔐

auth.js + auth.php

Full account system. bcrypt passwords, PHP sessions, SQLite database. Modal login on every page. Admin dashboard with login telemetry and account-linking for Gramma's page.

lurkers_play.html

Turn-based tactical game — humans vs. zombies. Canvas 2D renderer, PHP/SQLite backend. Proposal system, fog of war, LOS, zombie AI, traits, infection, observer animation.

map_editor.html

Tile painter for Lurkers maps. Separate layers: terrain, walls (N/E/S/W bitmask), objects (barrels, pillars, sandbags), and zones. Unified zoom with the game client.

scenarios.html

Scenario editor for Lurkers — set starting positions, assign units with portrait previews, define win conditions. Portrait images clipped to circle with faction rings.

codex.html

Admin UI for editing unit and trait definitions. Animated gold-tinted trait icons. Live stat display with raw vs. buffed values. Drag-to-assign traits, detection stats per unit.

profile.html

User profile page linked to the account system.

12
The Lurkers Game
↑ Top
The Biggest Feature Yet

Fire Emblem Meets the Zombie Apocalypse

After poker and driving games, the next challenge was the most ambitious: a full turn-based tactical game — humans vs. zombies, Fire Emblem style. Four human unit classes (soldier, scout, medic, sniper) face off against shamblers, runners, and bloated undead across hand-painted grid maps. Multiple players share control of the human side. The zombies run themselves.

The constraint: no game engine, no WebSockets, no frameworks. Just HTML Canvas, PHP, and a single SQLite file on shared hosting. The goal was a fully playable multiplayer game built entirely through conversation — designed, implemented, debugged, and balanced in about a week.

FOG OF WAR SIGHT 🪖 🕵️ 🕵️ ✓ 🧟 TURN 4 PHASE HUMAN ▶ READY ✓ lurkers_play.html — Canvas 2D, PHP/SQLite backend
👻

The Proposal System

Players don't move immediately — they propose destinations. A ghost with a ? badge appears; once committed it shows ✓ and the game already treats the unit as there for fog-of-war. When everyone hits READY, all moves and attacks resolve simultaneously server-side.

🧠

Zombie AI

The zombie phase runs entirely server-side. Each zombie independently checks sight (LOS-required), hearing (through walls), and bloodhound (global detection). Detected zombies charge and attack. Runners get +2 move on detection. Frenzy, infection, prone — all data-driven from the database.

🔦

Fog of War + LOS

Line of sight uses Supercover DDA — every tile the ray passes through is checked against wall edges (N/E/S/W bitmask) and opaque objects. Fog updates the moment you commit a proposal, not just at phase end. Zombie selection shows a red dashed sight box and amber dotted hearing box.

🧬

Traits & Status Effects

Body armor (40% damage reduction), scalpel (50% double-damage, lethal tag), undead (adds infection stacks — 2 HP/stack/turn), triage (heals + clears infection), volatile. All defined in the database; no hardcoded behavior in the engine.

⚡ Technical Details — SQLite on Shared Hosting ⚡
Technical Details

One File, One Server, One PHP Script

The entire game state lives in lurkers_game.db: tables for games, units, proposals, attack orders, players, and definition tables for unit types and traits. Every action — move proposal, attack declaration, ready signal — is a JSON POST to lurkers_game.php, which reads state, runs resolution logic, and returns the new state in one round-trip. No WebSockets. No push. Just polling.

Observer players poll every 2 seconds, comparing updated_at timestamps. When a phase resolves, a unified renderTransition() function plays combat animations for everyone — not just the player who clicked READY. The game genuinely feels multiplayer despite running on a $10/month shared host.

Round Two

Playtesting Reveals Everything

Once the core game was playable, the bugs came out of hiding. Zombies were moving through walls. Auto-attacks were landing on targets behind solid barriers. A runner appeared to phase through a wall entirely — it was actually going around the wall, but the start-to-end animation made it look like cheating. Each bug pointed to the same root cause: the game had no way to show its work.

The wall fixes were surgical. The LOS algorithm used && where it needed || — a diagonal step only blocked if both cardinal edges had walls, which let a ray slide through a horizontal wall at an angle. One character changed; the bug disappeared in both the JavaScript client and the PHP backend independently. The wander function, the auto-attack resolver, and the BFS pathfinder each got their own version of the same fix.

🔊

Sound as a Fog-of-War Mechanic

When a ranged human fires, a positional sound marker is saved server-side immediately — not at phase resolution. During the zombie turn, each zombie checks if the sound is within hearing range. If it is, the zombie charges toward the noise even if the shooter is hidden in fog. The marker clears at the start of the next human turn, so sounds are genuinely ephemeral: fire once, get chased once.

🧭

Step-by-Step Movement Animation

The "runner through a wall" confusion was the final push. The PHP backend now reconstructs the full tile-by-tile BFS path (tracking parent pointers) and sends it with every zombie move event. The client animates each step at 280 ms per tile — ease-in on the first tile, linear through the middle, ease-out on the last. A 5-tile frenzy sprint is now legible. No more teleportation.

🐛

Debug Tools Built Into the Game

A hidden debug bar (toggle with ~) grew into a real investigation toolkit: the SOUNDS checkbox renders 🔫 markers at every human gunshot position; the ⬇ HISTORY button downloads the full event log for the current game as JSON — every move, attack, infection tick, and death since turn 1. Bugs that were invisible became obvious within seconds.

⏱

Animation Timing Across Multiple Units

When multiple zombies move simultaneously, their animations stagger by 140 ms. But with variable-length paths — a shambler taking one step vs. a frenzy runner taking five — the wait time can't be fixed. The client now computes max(i × stagger + path.length × stepDur) across all moving units so it always waits for the last step of the slowest zombie before playing combat.

13
The Codex & the Art Pipeline
↑ Top
Tools Within Tools

Building an Admin UI — and an Art Pipeline

A data-driven game needs a data editor. codex.html became the admin panel for the entire Lurkers world: edit unit stats, assign traits, tune detection ranges, preview portraits. All changes write directly to the SQLite database.

But a game also needs art. Each trait — scalpel, chainsaw, body armor, bloodhound — got its own icon. The pipeline: generate 2×2 sprite sheets in Midjourney (zombie woodblock style), run slice_icons.py to cut them into individual PNGs, apply a gold tint in the codex renderer. Every new trait category meant a new set of prompts and another round through the pipeline.

UNITS 🪖 Soldier 🕵️ Scout 💉 Medic 🎯 Sniper 🧟 Shambler TRAITS 🔪 🪚 🦺 🏃 🩺 🐕 💉 🔫 💣 🎯 🧪 SELECTED TRAIT Body Armor — 40% damage reduction · tag: protection ATK — · RNG — · PROT 40%
✂️

slice_icons.py

Midjourney outputs sprite sheets — 2×2 by default, larger sheets named with a suffix (e.g. Sheet4x4). The script detects the grid size from the filename, splits the sheet into individual PNGs, and applies a CELL_INSET to crop out MidJourney's border frames.

🎨

Gold Tint in the Codex

All trait icons are stored as regular PNGs. The codex renderer applies a gold tint at draw time using canvas globalCompositeOperation — no need to regenerate images when the color scheme changes. Zombie-side traits use amber; human traits use teal.

📊

Live Stat Display

Raw stat (small, dim) next to buffed stat (large, bright gold) — inline, baseline-aligned. Assign a trait and the buffed column updates immediately. The trait assignment chips show × to remove; "+ ADD TRAIT" opens a dropdown of everything not yet assigned.

🖼️

Portrait Pipeline

Unit portraits are circular-clipped in both the scenarios editor and the game client. The scenarios editor shows the first portrait from the pool in the unit picker; dragging a unit to the map uses the portrait clipped to a circle with a teal (human) or amber (zombie) ring.

14
Claude Code — A New Way In
↑ Top
The Tool Evolved

From Chat Window to Living Inside the Code

The early chapters of this story were written in a chat interface. Paste the code. Get new code back. Paste it again. It worked — but the context window was a hard ceiling, and every session started cold.

Then came Claude Code: a VS Code extension where the AI lives directly inside the editor. It can read every file in the project, run terminal commands, make edits in place. No more copy-pasting. No more "here's the relevant section" — it just reads. The relationship between human and AI collaborator changed fundamentally.

lurkers_game.php — Brainrotters EXPLORER 📄 lurkers_play.html 📄 lurkers_game.php 📄 codex.html 📄 map_editor.html 📄 objects.js 🗄️ lurkers_game.db // resolve phase function run_zombie_phase $detected = []; foreach ($zombies if (has_los(...)) $detected[] = $z; } CLAUDE CODE I can see the issue — PDO returns strings, so cast coordinates: (int)$z['x'] MEMORY Lurkers schema, turn system, detection stats, trait model… Claude Code — reads files, edits in place, remembers across sessions
📁

Direct File Access

No more copy-pasting code into a chat window. Claude Code reads any file in the project directly — all 2500 lines of lurkers_play.html, the PHP backend, the database schema. The full picture, always in context.

🧠

Memory Across Sessions

A persistent memory system captures what matters between conversations — game architecture, design decisions, gotchas (like PDO returning strings instead of ints). The next session starts informed, not cold.

🔁

Edit in Place

Changes go directly into the file. The diff is visible in the editor. No intermediate step of "now paste this back." The feedback loop between human and AI collapsed from minutes to seconds.

🌐

Still Just a Family Website

All of this — the tactical game, the art pipeline, the codex admin UI, the multiplayer observer system — runs on a $10/month shared host with no frameworks, no CI/CD, no deployment pipeline. WinSCP. FTP. Done.

"Every new capability the tool gets is immediately visible in what the site becomes. The tool got better, so the site got bigger. That's a new kind of creative relationship."