Blog/The Feature That Almost Broke Us: Real-Time Board Threat Detection
DevelopmentBehind the Scenes

The Feature That Almost Broke Us: Real-Time Board Threat Detection

Shane Foster·October 20, 2025·6 min read

The threat overlay was supposed to take a weekend. It took two weeks, one full rewrite, and a debugging session that started at 11 PM and ended somewhere around 3 AM when I finally found the bug — a single character error in a FEN parsing function that was causing kings to be treated as invisible to the threat detection algorithm.

We fixed it. The feature shipped. And it remains one of the most-used parts of D4 Chess Club™. But the journey there is worth documenting, because it illustrates something true about software development: the features that seem simple often hide the most complexity.

What the Feature Is

The threat overlay renders additional information on top of the chess board as you step through a game. Attacked pieces are highlighted. Squares under control are indicated. King safety is visualized — a red glow around the king when the position is dangerous, dimmer the safer it is. The goal is to let you see the tactical landscape of the position before your brain has consciously registered it.

From a user perspective, it's a layer of colored squares and highlights. Implementing it correctly required building a complete legal move generator in TypeScript, running server-side on each position.

The First Approach (Wrong)

My first instinct was to ask Stockfish for this information — after all, the engine knows everything about a position. The problem is that Stockfish doesn't expose a clean API for "which squares are attacked by which pieces." It evaluates positions and returns best moves. Extracting threat data from it would require either parsing engine output in ways it wasn't designed for, or running dozens of probe positions for each single board state we wanted to visualize.

That was too slow. Threat visualization needs to feel instant — the highlight should appear as soon as the position loads, not 300 milliseconds later. We needed our own threat detection logic.

Building the Piece Attack Maps

The second approach was correct in principle but wrong in implementation. I wrote a TypeScript function that, given a FEN string, would generate attack maps for every piece on the board — which squares each piece controlled, what it attacked, where it could move.

For most pieces this is straightforward. A knight on e4 attacks d2, f2, c3, g3, c5, g5, d6, f6 — always. No board state needed. A bishop on d4 attacks along diagonals until it hits a piece or the edge of the board. This required "ray casting" — walking along each diagonal and stopping when you hit something.

Kings, pawns, and en passant introduced complications. Pawns attack diagonally but move forward — two different behaviors. Kings have to be excluded from certain squares where the opponent has control, which requires knowing the opponent's full attack map first. En passant targets a square that isn't occupied by the piece being captured. Each of these is a special case, and each special case is a potential bug.

The 3 AM Bug

The bug that cost me the most time was subtle. My FEN parser was correctly identifying the king's position. But when checking whether a square was under attack by the king, I was looking for the piece character 'K' or 'k' — except in one code path, I had accidentally written 'K' only. Black's king (lowercase 'k') wasn't being recognized as a threat source in some calculations.

This meant positions where black had a king move that would have captured white's piece weren't flagging as captures. The threat visualization was silently wrong in any position where black's king was the relevant threatening piece.

It took three hours to isolate because the cases where it manifested were specific — king in a certain zone, with certain pieces adjacent — and my test positions weren't hitting them reliably. Eventually I wrote a brute-force test that compared our attack maps against a reference implementation for 10,000 random positions. That's when it showed up consistently, and the fix was a single character.

What It Taught Us

Two things. First: chess is surprisingly complex to implement correctly. The rules feel simple. The edge cases are not. Any time you're writing game logic, you need exhaustive tests against a reference implementation, not just the happy-path cases you think of first.

Second: the features worth building are often the ones where the difficulty is front-loaded. The threat overlay is hard to implement correctly. Once it's correct, it just works — no ongoing maintenance, no drift, no user-reported edge cases. That upfront investment pays off every time a player uses D4 Chess Club™ to step through a position and immediately sees the pin they missed. That's what makes it worth it.

Related Reading

Ready to improve?

Try D4 Chess Club™ free — no credit card required

AI coaching, 54,000+ puzzles, Stockfish analysis. Built for the player who wants to get better, not just play more.

Start Training Free