At this point the MVP holds up and the CC0 models are in. What comes next is not more features — it is turning playable into fun. Two tracks, run in parallel.
Close the loop: every state needs a reaction
Spawn → fight → die → respawn → scoreboard → progression. Every one of those states needs unmistakable feedback. I filled them in one by one:
- Spawn protection — a couple of seconds of invulnerability on respawn so you cannot be pinned at the spawn point (firing cancels it, so it cannot be abused).
- Kill feedback — a skull popup, a sound, and the hit marker expanding.
- Headshots — higher damage, a gold damage number, its own sound.
- Death — the body plays its death animation, then sinks and hides. Otherwise corpses pile up everywhere.
- Progression — coins, levels, unlockable weapons and skins. A positive-feedback curve to climb.

// Spawn protection: brief invulnerability on respawn, cancelled by firing
function respawn(e) { e.hp = 100; e.alive = true; e.invuln = 2.5; }
function tryShoot() { /* ... */ player.invuln = 0; } // firing drops the shield
function applyDamage(t, dmg) { if (t.invuln > 0) return; /* ... */ }First-person feel: the details that decide everything
None of these landed on the first try. Every one was "change a version → produce before-and-after screenshots → nudge again":
- Weapon position — pull it toward the lower centre of the screen, held in two hands. Do not let it float in the top right.
- Recoil — the muzzle kicks up on fire (pitch up a little, then ease back down).
- Dynamic spread (bloom) — sustained fire widens the spread, and the crosshair opens up and shakes with it; it recovers when you stop.
- Reload and sprint — the weapon dips and rises on reload, and lowers while sprinting.
// Dynamic spread: every shot grows bloom, it decays per second while you hold fire
bloom = Math.min(bloomMax, bloom + w.recoil); // accumulate on fire
bloom = Math.max(0, bloom - dt * bloomMax / 0.8); // recover each frame
const spread = baseSpread + bloom; // actual bullet spread
crosshairGap = 4 + bloom * 1.5; // crosshair opens up with itSmall steps, or you learn nothing
Feel comes from short iterations. Each round, change a handful of parameters, have the AI produce before-and-after screenshots, and vote with your eyes. Change too much at once and you cannot tell which variable did what — which means the next round is a guess too.
In one lineFun is not one clever idea. It is every part of the loop having a reaction.
The prompts I used
The MVP and the assets are in place — time to polish. Two tracks: 1) Close the gameplay loop — add: spawn protection (2.5s invulnerability on respawn, cancelled by firing), kill feedback (popup + sound + hit-marker expansion), headshots with higher damage and a gold damage number, bodies that sink and hide after the death animation, and a coins / levels / unlocks progression curve; 2) Polish the first-person feel — weapon pulled to the lower centre and held in two hands, muzzle kick on fire, dynamic spread on sustained fire (crosshair opens and shakes, recovers when you stop), reload and sprint animations. For each version, give me before-and-after screenshots. Small steps, one round at a time.
Entering the "find the fun" phase: 1) Walk the gameplay loop state by state and check it is closed — every state (spawn / hit / death / results / progression) needs clear visual or audio feedback. Fill in whatever is missing; 2) Polish the feel of the core action (input response, camera feedback, audio-visual feedback). Move in small changes with comparable before-and-after results, and only adjust a few parameters per round.
The game I built
This is what came out the other end of all of it. It opens in the browser, nothing to download — go and break it.