AgentGames

Search titles, descriptions, models and tags. Press Enter to see results.

⚡ Guide

How to make a reflex game with AI

If your reaction game is handing out times under 100ms, nobody is that fast and nothing is broken. You are counting from a moment the player could not see.

Updated 5 October 2026 7 min read By the Agent Games team
Jump to a section
  1. Four steps to a timer you can trust
  2. Start the count at the frame, not at the variable
  3. Read the time off the event, not off the clock
  4. Reject anything faster than a person
  5. Randomise the wait or you are not testing reaction
  6. What to show at the end of a run
  7. Common questions
  8. Where to go next

Ask for the cue time to be taken inside the animation frame that drew the cue. Then read the player's press off the input event, rather than off the clock when your handler runs. A reflex game is one shape, one key and one subtraction. Getting that subtraction right is the whole craft of the genre.

Your times are too good because you started counting too early

A first build sets a cue time when its delay timer fires, then compares it with the clock when a key arrives. Between those two moments sits the rest of the frame, the wait for the next refresh and the screen itself. All of that gets subtracted from the player's score, which is how a browser ends up reporting a reaction nobody can produce.

Four steps to a timer you can trust#

Name the two moments before you write anything

The frame the cue was drawn in, and the instant the input happened. Every fault in this genre is one of those two being wrong.

Try to cheat your own game

Press as the delay runs out, hold the key, hammer it. If any of that produces a good score, the timing is measuring you rather than your reaction.

Check the floor

A result under 100ms is not a fast player. It is proof the count started before the cue could be seen.

Only then make it a game

Rounds, a best time, a streak. All of it is built on a number, so the number has to be right first.

PromptCopy this, paste it, send it
Build a reaction time game as ONE self-contained .html file. No
frameworks, no libraries, no image files.

THE ROUND: a dot sits still in the middle of a dark canvas. After a
random wait, the dot starts moving. The player presses SPACE, clicks or
taps as soon as they see it move. A run is five rounds, then a summary.

THE WAIT: pick a fresh random delay for every round, somewhere between
1200ms and 3500ms. Never use a fixed delay and never use a countdown,
because either lets the player anticipate instead of reacting.

TIMING, and this is the part that matters most:
- Drive everything from requestAnimationFrame and keep the timestamp
  the callback is given.
- Record the cue time as that timestamp, in the exact frame where the
  dot is first drawn in its moved position. Do not record it when the
  delay expires.
- When the input arrives, use the timeStamp carried on the event
  object, not Date.now() and not a fresh reading of the clock inside
  the handler.
- Report the difference between those two numbers in whole
  milliseconds.

GUARDS:
- An input before the dot moves is a false start. Say so, void the
  round and replay it.
- A result under 100ms is a guess rather than a reaction. Reject it,
  say why, and replay the round.
- Ignore repeat events from a key being held down, so hammering the
  key cannot win.

SUMMARY: show every round, the best, and the average of the rounds that
counted. Keep the best average in localStorage and show it beside the
new one.

Move the dot by the seconds elapsed since the previous frame, limited
to 0.05 so a tab coming back from the background cannot jump it across
the canvas. Add canvas { touch-action: none; } to the CSS. Gather
MIN_WAIT, MAX_WAIT, ROUNDS, IMPLAUSIBLE_MS and DOT_SPEED into one
CONFIG object at the top.

The timing block is not tuning, it is the specification, so leave every line of it as written. MIN_WAIT, MAX_WAIT and ROUNDS are yours to move. If you widen the wait, widen the top end rather than the bottom, because a short minimum is what makes false starts pay off.

Start the count at the frame, not at the variable#

The usual shape of a first build is a delay timer that sets a cue time and flips a flag, with the drawing happening later in whatever frame comes next. The cue time is therefore earlier than the cue. Everything between the two is a gift to the player.

Where the stopwatch starts The same reaction test, timed from two different moments in the same frame budget. Timed from the variable cueAt = Date.now()Set inside the delay timer.Starts before anything was drawn.Happily reports times under 100ms. Timed from the frame cueAt = frameTime // from rAFSet where the cue was drawn.Starts from something on screen.Reports times a person could reach. A reaction time is only as honest as the moment you chose to start counting from.
The fix is a line moved rather than a line added. The cue time is assigned inside the frame callback that draws the cue, using the timestamp that callback was handed.

The timestamp passed to an animation frame callback is the right value to keep. It is measured on the same clock as performance.now(), so you can subtract one from the other without converting anything. Taking a fresh reading at the top of your own code is close, and the supplied timestamp is what the browser actually scheduled.

There is still a gap you cannot close. A frame you draw is handed to the compositor and reaches the screen afterwards, and the display adds its own delay on top. So the count still starts slightly before the player could see anything. That residue is small and consistent, which is all a score needs to be.

Read the time off the event, not off the clock#

The other half of the subtraction is the moment the player acted, and calling the clock inside your handler is not it. Your handler runs after the browser has queued the event, finished whatever it was doing and got round to you. On a busy frame that wait is visible in the result.

Every input event carries a timeStamp property recording when the event was created, which is far closer to the key going down. In current browsers that value sits on the same clock as the animation frame timestamp, so the two subtract cleanly. Using it costs nothing and removes a source of noise you cannot otherwise see.

  • Never mix the two clocks. Subtracting a frame timestamp from a wall clock reading gives a number with no meaning. Pick one and keep every measurement on it.
  • Ignore the repeat flag on a held key. A key held down fires again and again, and one of those repeats will eventually land just after a cue and look like a superhuman reaction.
  • Listen for the press, not the release. The release is a second decision and adds however long the player kept their finger down.
  • Accept a tap as well as a key. A touch event carries the same kind of timestamp, so the same subtraction works, and the game then means something on a phone.

Reject anything faster than a person#

A plausibility floor is the cheapest test you will ever write and it catches almost every fault in this genre. Reacting to something you have to see takes a couple of hundred milliseconds for most people, and the quickest are not close to half of that.

What a reported time really contains Five things stacked between your delay timer firing and the number on the screen. 1 Your delay timer Fires late by an unknown amount. Not the cue. 2 The next frame A cue can only appear on a refresh boundary. 3 Display latency Pixels reach the eye after the frame is handed over. 4 The person Seldom under 200ms for something they have to see. 5 The input event Stamped when it happened, not when you handled it. You cannot remove the first three. Starting the count at the frame stops them inflating it.
Rows one to three are the reason a naive build flatters everybody. They sit inside the measurement rather than inside the player, so moving the start of the count removes them.

Treat a result under 100ms as a guess rather than a score. Void the round, say plainly that it was too fast to be real, and replay it. Players accept this immediately, because they know whether they were guessing, and it makes your leaderboard mean something.

The floor is also a test of your own code. If a careful player who waited for the cue trips it, the timing is wrong somewhere, not the player. Keep the check in the finished game and it keeps telling you that for as long as the game exists.

Randomise the wait or you are not testing reaction#

A fixed delay before the cue turns a reaction test into a rhythm test. After a few rounds the player presses from memory and beats their own nervous system. The same goes for a visible countdown, a progress bar or any sound that marks the approach.

Cue designWhat it measuresWhat the player learns to do
A fixed delay every roundTiming from memoryCount silently and press on the beat
A countdown, then the cueAnticipationPress on the last tick, every time
A random delay, no warningReactionWatch, and wait, which is the point
Random, with a false start ruleReaction under restraintStop guessing, because guessing costs a round
Random, no upper boundPatienceLose interest before the cue arrives

The fourth row is the one worth building. A random wait makes guessing tempting, and a false start rule is what makes it a bad idea.

Keep an upper bound on the wait. A delay that occasionally runs for many seconds makes the game tiring rather than tense, and the player's attention has already gone by the time the cue lands. A range of a second or two at the bottom and three or four at the top covers it.

What to show at the end of a run#

One reaction time is mostly noise. Several rounds and a simple summary turn it into something a player can compare, both with their own past runs and with anybody else's.

  • Show the rounds individually. Players want to see the one they fumbled. A single averaged number hides the shape of the run and feels arbitrary.
  • Average the rounds that counted. Voided false starts and rejected guesses should be visible but excluded, or one bad round defines the whole result.
  • Keep the best average, not the best single round. A best single time rewards a lucky guess that slipped past your floor. An average rewards being consistently quick.
  • Say what the number does not include. A short line noting that the screen and the browser add a little is honest, and it heads off the argument about whose monitor is faster.

⚡Reflex games written by AI

Common questions#

Why does my reaction game report times under 100ms?

Because the count starts before the cue was drawn. The usual cause is taking the cue time where the delay timer fires. Move it into the animation frame callback that draws the cue and use the timestamp that callback receives.

Should I use Date.now() or performance.now()?

Neither, if you can avoid reading the clock at all. Use the timestamp handed to the animation frame callback and the timeStamp on the input event. Both sit on the same monotonic clock, and a wall clock reading can be adjusted underneath you.

How fast can a human really react?

To something they have to see, a couple of hundred milliseconds is a normal result and the very quickest are well above 100ms. That is why a floor at 100ms catches guesses and timing faults without ever rejecting a genuine score.

How do I stop players from guessing?

Randomise the delay before every cue, remove any countdown or warning sound, and void the round on an input that arrives before the cue. Once guessing costs a round rather than winning one, it stops.

Does the screen refresh rate affect the score?

Yes, a little, because a cue can only appear when a frame is drawn. Timing from the frame rather than from your delay timer keeps that effect small and the same for everyone on the same screen. It cannot be removed entirely.

Can a reflex game work on a phone?

Well, and often better. A touch carries the same kind of timestamp as a key press, so the subtraction is identical. A thumb already resting on the glass also has less travel than a hand on a keyboard.

Where to go next#

Once the number survives your own attempts to cheat it, publish the game. It gets a page recording the model and the prompt behind it, and times measured this carefully are worth putting on the leaderboard. The reflex games here are a quick way to find out which ones flatter you.

The general build method is in how to make a game with AI. Saved times across different machines come up again in the racing guide. For the restart loop that decides whether anyone plays a second run, see the arcade guide, and the casual guide covers teaching a rule without text.