DEV Community

Shahen
Shahen

Posted on • Edited on

Building a Horror Typing Game Where Blinking Kills You

Introduction

Imagine you're typing as fast as you can, trying to complete a horror story. But there's a catch. Every time you blink, a monster teleports closer. And you can't stop blinking.

This game is designed to strengthen your typing ability through immersive, pressure-based learning. By eliminating the habit of looking at the keyboard and encouraging rapid, accurate input, it helps you develop true touch-typing proficiency in a way traditional tools don’t.

Main Menu

Intro

Game

Cases

Case

Leaderboard

Core Pillars

  • A full typing game with character-by-character validation
  • Daily AI generated stories(cases) and typing challenges
  • Browse old cases from the asylum, read spooky stories and play the challenge
  • Real-time webcam face detection with calibration system for accurate blink detection
  • A Unity WebGL game for a more immersive experience
  • Two-way communication between React and Unity
  • FMOD audio integration for that horror atmosphere
  • Leaderboard with realtime player high scores

The Tech Stack

Frontend:

  • Next.js 15 with App Router
  • React 19
  • TypeScript
  • Tailwind CSS v4
  • shadcn/ui components
  • Zustand for state management
  • React-unity-webgl package
  • Google MediaPipe

Backend:

  • Convex backend
  • Authentication
  • Anthropic AI SDK
  • Recraft image generation API

Game Engine

  • Unity 6
  • FMOD Studio for audio

Development Tools:

  • Kiro IDE with specs, steering rules, agent hooks, and MCP servers

The Team

We're two brothers from Mauritius with a shared passion for gaming and programming. Over the years, we've spent countless hours experimenting, learning, and building. Whether it's software, web platforms, or the games we've always dreamed of creating. Our goal is simple: make things people genuinely enjoy using and playing.


Why Kiro Specs Changed Everything

Hackathons demand quick decisions under pressure. Features pile up fast, and without a solid direction, things fall apart just as quickly. When issues like unstable blink detection and unpredictable Unity behaviour began slowing progress, Kiro Specs provided the clarity and structure needed to keep the project on track.

Finishing a project of this scope in just 18 days simply wouldn’t have been possible without it.

The Three-Document Pattern

Every feature we built followed the same pattern:

  1. requirements.md - What are we building? What does "done" look like?
  2. design.md - How are we building it? What are the algorithms?
  3. tasks.md - What are the exact steps to implement it?

This might sound like overkill for a hackathon, but it actually saved us time. When you're sleep-deprived at 2 AM and trying to remember how the Eye Aspect Ratio calculation works, having it documented in design.md is a lifesaver.

Real Example: The Blink Detector Spec

Let's look at how we specced out the blink detection system. This was one of our most complex features.

From requirements.md:

### Requirement 3: Eye Aspect Ratio Calculation

**User Story:** As a developer, I want the system to calculate Eye Aspect Ratio (EAR) 
from facial landmarks, so that eye openness can be quantified.

#### Acceptance Criteria

1. THE system SHALL use MediaPipe face mesh landmark indices for left eye 
   (33, 133, 159, 145, 160, 144) and right eye (362, 263, 386, 374, 385, 380)
2. WHEN facial landmarks are detected, THE system SHALL calculate EAR using 
   the formula: (vertical1 + vertical2) / (2.0 * horizontal)
3. THE system SHALL calculate Euclidean distance in 3D space (x, y, z) for landmark pairs
Enter fullscreen mode Exit fullscreen mode

Notice the EARS format (Event-Action-Response-State). It's precise. It's testable. When we implemented the hook, we knew exactly what success looked like.

From design.md:

### P4: Blink Detection State Machine

**Property:** A valid blink follows the state machine: 
open → closing → closed (2-15 frames) → opening → open

**Verification:** State transitions follow defined rules, 
blink count only increments on complete cycle

**Covers:** AC4.1, AC4.2, AC4.3
Enter fullscreen mode Exit fullscreen mode

The design doc gave us the algorithm. We didn't have to figure out blink detection logic on the fly. We had already thought through edge cases like "what if someone holds their eyes closed for 3 seconds?" (that's drowsiness, not a blink).

The Monster Teleportation System

Here's another example. The monster needed to teleport closer with each blink, then sprint at you on the final one.

From requirements.md:

### AC3: Blink Teleportation

**Given** the player has more than 1 life remaining  
**When** the player blinks (blink event received from React)  
**Then** the player should lose 1 life  
**And** the monster should teleport closer to the camera by a calculated distance  
**And** the calculated distance should be: (startingDistance - goalDistance) / (lives - 1)
Enter fullscreen mode Exit fullscreen mode

From design.md:

### P3: Teleport Distance Accuracy

**Property:** Each teleport moves monster by exactly 
(startingDistance - goalDistance) / (lives - 1)

**Verification:** Distance change per blink equals calculated teleportDistance ± 0.01

**Covers:** AC3
Enter fullscreen mode Exit fullscreen mode

With 5 lives, starting at 10 units and a goal of 2 units, each blink moves the monster exactly 2 units closer. Math done once, documented forever.

Steering Rules: Your AI Copilot's Memory

Specs handle individual features. But what about project-wide standards? That's where steering rules come in.

We created steering files for:

  • Project vision - Understand the grand vision of the project
  • Unity development standards - Unity 6.1 patterns, Input System usage, lifecycle methods
  • State management - When to use Zustand vs useState, store patterns
  • Import aliases - Always use @/ paths, never relative imports
  • shadcn/ui components - Always check if a component exists before building custom

Here's a snippet from our Unity steering rule:

### Cache Component References

// ✅ Correct - Cache in Awake/Start
private Rigidbody rb;

void Awake()
{
    rb = GetComponent<Rigidbody>();
}

// ❌ Wrong - GetComponent every frame
void Update()
{
    GetComponent<Rigidbody>().AddForce(Vector3.up);
}
Enter fullscreen mode Exit fullscreen mode

Every time Kiro helped us write Unity code, it followed these patterns automatically. No more explaining the same best practices over and over.

Agent Hooks: Automation That Actually Helps

We set up an agent hook that validates spec traceability whenever we save a spec file:

{
  "name": "On Spec Update - Validate Traceability",
  "when": {
    "type": "fileEdited",
    "patterns": ["**/{requirements.md,design.md,tasks.md}"]
  },
  "then": {
    "type": "askAgent",
    "prompt": "Analyze the spec files and validate traceability..."
  }
}
Enter fullscreen mode Exit fullscreen mode

Every time we saved a spec file, Kiro would check:

  • Does every requirement have a corresponding property?
  • Does every property have a task that implements it?
  • Are there any orphaned elements?

This caught gaps we would have missed. Like when we added a new acceptance criterion but forgot to update the design properties.

MCP Integrations: The Secret Weapons

Kiro's MCP (Model Context Protocol) support turned out to be one of our most valuable tools. We integrated several MCP servers that dramatically improved our development workflow.

Browser Automation for Real Testing

Since our game runs in the browser and involves webcam access, blink detection, and Unity WebGL, we needed to test everything in a real browser environment. Checking if code compiles isn't enough when you're dealing with MediaPipe initialization, webcam permissions, and Unity-React communication.

With browser automation, Kiro could actually:

  • Open the game in a real browser
  • Check console logs for errors
  • Verify that components were rendering correctly
  • Test the webcam permission flow
  • Debug Unity WebGL loading issues

This was especially helpful for catching issues that only appear at runtime. Things like MediaPipe failing to initialize, Unity events not reaching React, or the webcam feed not displaying. These are problems you simply can't catch without running the app in an actual browser.

Convex Documentation via Context7

We relied heavily on the Convex documentation through Context7 MCP. Building the backend for daily story generation, leaderboards, and case management became much smoother when Kiro had direct access to up-to-date Convex docs.

Instead of context-switching to browser tabs and copy-pasting examples, we could ask Kiro to implement features and it would pull the correct patterns directly from the documentation. This helped us implement features like scheduled story generation and real-time leaderboard updates with almost no mistakes.

The Development Loop

The combination of these MCP integrations created a tight feedback loop:

  1. Write code with Kiro (using Convex docs for backend patterns)
  2. Kiro opens the browser and tests the changes
  3. If something breaks, Kiro reads the console logs
  4. Fix and repeat

During a hackathon where every minute counts, automating this loop saved us hours of manual testing and debugging.

The Typing Game: Character-by-Character Precision

The typing game needed to feel responsive. Every keystroke matters when a monster is breathing down your neck.

From our design doc:

### Natural Typing with Error Correction (Improvement 7)

**Core Principle**: Users can type freely, including mistakes. 
The system provides visual feedback for errors but allows the full input to remain, 
enabling natural self-correction with backspace.

**Typing Behavior**:
1. Users can type any characters, including incorrect ones
2. The cursor advances with each character typed (correct or incorrect)
3. Visual feedback shows which characters are correct (green) and incorrect (red)
4. Users can use backspace to delete characters and correct their mistakes
5. Only when the entire word is typed correctly can users press Space/Enter to advance
Enter fullscreen mode Exit fullscreen mode

We went through 7 iterations of the typing mechanics. Each improvement was documented, so we could trace why we made certain decisions.

Unity and React: Two Worlds, One Game

The trickiest part was getting Unity and React to talk to each other. Unity handles the 3D horror scene. React handles the typing game and blink detection.

From our interop spec:

### Requirement 2: GameIsReady Event

**User Story:** As React, I want to know when Unity is ready, so that I can show the game.

#### Acceptance Criteria

1. THE React.jslib SHALL define a GameIsReady function
2. THE function SHALL call window.dispatchReactUnityEvent("GameIsReady")
3. THE MainMenuManager SHALL call GameIsReady() in Start()
4. THE call SHALL only execute in WebGL builds (not in Editor)
Enter fullscreen mode Exit fullscreen mode

Simple, clear, testable. When something broke in the Unity-React communication, we knew exactly where to look.

AI-Generated Horror Stories

Every 24 hours, Claude generates a new horror story for players to type. The spec made this straightforward:

### Requirement 2: Story Generation Action

#### Acceptance Criteria

1. WHEN the generation action runs, THE system SHALL call Claude API with the prompt
2. THE system SHALL use Claude tool use to enforce the Story interface format
3. IF generation fails, THEN THE system SHALL retry up to 3 times with 5 minute delays
Enter fullscreen mode Exit fullscreen mode

The story has a title, introduction, and 8 progressive chapters. Each chapter has a difficulty level. Players type through the story while the monster gets closer with every blink.

What We Learned

Specs aren't bureaucracy. They're thinking tools. Writing requirements forces you to actually understand what you're building before you build it.

Steering rules compound. Every rule we added made Kiro more helpful. By the end of the hackathon, it felt like working with a teammate who actually remembered our coding standards.

Agent hooks catch mistakes early. The traceability check saved us from shipping incomplete features multiple times.

Documentation is debugging. When something broke, we could read our own design docs instead of reverse-engineering our code.

Try It Yourself

Type to Death is live. See how long you can survive without blinking.
Play Game

And if you're building something complex, give Kiro specs a try. Start with one feature. Write the requirements, design the solution, break it into tasks. You might be surprised how much clearer everything becomes.


Built during Kiroween Hackathon by two brothers from Mauritius who probably should have blinked less during testing.

Top comments (0)