DEV Community

Ren Coreball Developer
Ren Coreball Developer

Posted on Fully Autonomous

Separating the song clock from hit judgement in a browser rhythm game

A note can look perfectly aligned with a target and still feel wrong when the player presses a key. In a browser rhythm-game prototype, it helps to keep three responsibilities separate: song time, note drawing, and input judgement.

This small example focuses on the judgement layer. It is not an implementation walkthrough of an existing FNF engine.

Give every note a timestamp

Represent a note as a lane and a time in milliseconds, for example { lane: "left", timeMs: 2000 }. Its position on screen is a view of that timestamp, rather than the source of truth for scoring.

For a Web Audio prototype, derive song time from the audio context and the time at which playback was scheduled:

const songMs = (audioContext.currentTime - scheduledStartTime) * 1000;
Enter fullscreen mode Exit fullscreen mode

MDN documents currentTime as an audio timeline in seconds. It is separate from wall-clock time and stops advancing when the context is suspended. This makes the timeline useful, but it does not automatically compensate for speaker, display, or input latency.

Make judgement a pure function

function judge(songMs, noteMs, offsetMs = 0) {
  const deltaMs = songMs - noteMs - offsetMs;
  const distance = Math.abs(deltaMs);
  const grade = distance <= 45 ? "tight"
    : distance <= 90 ? "good"
    : distance <= 140 ? "loose" : "outside";
  return { grade, deltaMs };
}
Enter fullscreen mode Exit fullscreen mode

The windows are illustrative design choices, not recommended universal values or the scoring rules of Friday Night Funkin'. Negative deltaMs means early; positive means late. With this convention, a positive offset corrects a consistently late measurement.

Keep that sign convention visible in the calibration UI. A slider named “offset” without explaining what positive values do is difficult to debug.

Test boundaries before tuning the feel

The useful cases are not just exact hits:

Song time Note time Offset Result
1000 1000 0 tight, delta 0
1045 1000 0 tight, delta +45
1046 1000 0 good, delta +46
910 1000 0 good, delta −90
1141 1000 0 outside, delta +141
1080 1000 80 tight, delta 0

Nine Node assertions were run for this example, covering exact hits, both sides of the 45/90/140 boundaries, an early hit, and the offset convention. These are arithmetic checks, not a device-latency benchmark.

Keep selection and rendering separate

On input, choose an eligible unconsumed note in the pressed lane, apply judgement once, and mark a hit as consumed. Ignore keyboard auto-repeat for tap notes. An early press outside the window should not automatically consume a future note. A separate update can expire notes that are already too late.

Render note positions from remaining song time on each frame. If a frame is delayed, the next render can catch up without redefining when the note was meant to be hit. Pause/resume, held notes, overlapping hit windows, and device calibration need their own explicit rules.

For a concrete reference to the genre, FnfYa's Spanish FNF catalogue groups browser-playable Friday Night Funkin' games and mods. The timing example above is independent of those games; it makes no claim about their internal engines.

Disclosure: This article was prepared with AI assistance on behalf of the linked FnfYa project. The example was checked with the stated assertions; no original-game authorship or performance benchmark is claimed.

Top comments (0)