How to Debug an AI Web Board Game: A Step-by-Step Guide
A practical guide to vibe-debugging a browser-based card game: from reproducing a bug to checking synchronization. No magic, only verifiable steps.
Vibe-debugging is debugging by external symptoms, when the code is generated by a model and not fully understood. Below is a set of actions that help find the causes of bugs in a browser board game. The guide is not about a specific engine, but about the method.
What You Will Need
- A browser with DevTools (Chrome, Firefox, or Edge).
- Access to the repository and the ability to run the project locally.
- A console with server and client logs.
- Two browser windows or two devices to check synchronization.
- A list of known bugs with reproduction steps.
- Patience: one bug — one experiment.
Step 1. Capture the Bug as a Scenario
Open DevTools (F12) and record every action. Example scenario:
- Open the game in two windows.
- In window A, select a card in hand.
- Drag it onto the table.
- Release it.
- Check whether the card appeared in window B.
If the bug is intermittent, add temporary logs to the code. For example, in the card drop handler:
function onCardDrop(cardId, x, y) {
console.log('[drop]', { cardId, x, y, t: Date.now() });
sendMove({ type: 'playCard', cardId, x, y });
}
Logs must have timestamps. Without them, you cannot distinguish network delay from a rendering error.
Step 2. Separate the Visual Part from the Network Part
The problem may be in rendering, in data transmission, or in the rules. Check them one by one.
First, check whether the event reaches the server. In the server console, add a log of incoming messages:
ws.on('message', (raw) => {
const msg = JSON.parse(raw);
console.log('[ws in]', msg.type, msg);
});
If the message arrives but the card does not appear for the opponent — look for an error in the broadcast or in the client handler. If the message does not arrive — look at the client-side sending code.

Step 3. Check the Board State Before and After the Move
Take a snapshot of the state before and after the action. In the client, you can print it to the console:
function dumpState(label) {
console.log(label, JSON.stringify(gameState, null, 2));
}
Call dumpState('before') before the move and dumpState('after') after. Compare the fields: card position, whose turn it is, score. If the state changes where it was not expected to, the bug is in the rules logic.
Step 4. Reproduce the Move Manually via the Console
If the bug does not reproduce with the mouse, send the event directly. This helps separate the UI from the logic. Example for a WebSocket connection:
const ws = new WebSocket('ws://localhost:8080');
ws.onopen = () => {
ws.send(JSON.stringify({ type: 'playCard', cardId: 'card-1', x: 100, y: 200 }));
};
Take the address and message format from the client code. If everything works when sent manually, the problem is in the mouse handler or in the coordinate calculation.

Step 5. Check Synchronization of Two Clients
Open two windows. In each, run in the console:
console.log('client id', window.clientId);
If clientId is not defined, add it on connection. Then compare the state after the same move. Desynchronization is often caused by one client applying the move locally while the other waits for the server. Check whether the application is duplicated.
Step 6. Isolate the Game Rules into a Separate Function
Rules should be pure functions: state and move as input, new state as output. Example:
function applyMove(state, move) {
if (move.type !== 'playCard') return state;
const next = structuredClone(state);
next.table.push({ cardId: move.cardId, x: move.x, y: move.y });
next.turn = state.turn === 'A' ? 'B' : 'A';
return next;
}
Such a function is easy to test without a browser. Write several calls in the console and check the result. If the rules work in isolation but break in the game — look for where the state is mutated outside the function.
Step 7. Look at the Seams Between Movements
In the article, the author notes that they see not the card, but the seams between movements. This is a sign that animation and logic live separately. Check whether the animation starts before server confirmation. If so, jerks are possible on rollback. The solution is optimistic update with rollback based on the server response. But first make sure the response arrives.
Typical Mistakes and How to Recognize Them
- Logs without timestamps. You cannot distinguish network delay from slow rendering. Add
Date.now()to every log. - Checking on only one client. Synchronization is not verified. Always open two windows.
- State mutation in the handler. Symptom: after canceling a move, the state does not revert. Look for direct assignments instead of creating a copy.
- Mixing UI and rules. Symptom: the bug reproduces only at a certain window size. Move the rules into separate functions.
- Ignoring message order. Symptom: the card appears for one player earlier than for the other. Check whether events are sent in parallel without a queue.
If there is no data about a specific cause, do not guess. First collect logs, then change the code. One experiment — one hypothesis.