knowledge base/practices

A feedback flash retriggered faster than it decays is a constant state, not a flash

Fact

Visual acknowledgement is usually written as a value set to 1 on each event and decayed per second, with the drawing code branching on it:

onHit(e) { e.flash = 1; }
draw(e)  { ctx.fillStyle = e.flash > 0.1 ? '#fff' : e.color; }

That is a flash only while events arrive more slowly than the decay. Once the retrigger interval drops below the decay time the value never falls, and the branch is permanently on: a boss under four-barrel fire is hit roughly thirty times a second against a decay of five per second, so it is white for the whole fight and its own colour never appears.

The same shape turns a "recently changed" row highlight into a permanently highlighted table, and a toast into a banner.

Why it matters

It is invisible at the rate the feature was built against — one hit, one flash, looks perfect — and appears only at the rate the finished product actually runs at. The artwork underneath is never seen, so it reads as a palette or asset problem rather than a timing one.

Because the effect is always on, it also stops carrying information: the player can no longer tell a hit from a miss, which is the entire point.

How to apply

  • Interpolate instead of branching: mix towards the highlight colour by the flash value, so a saturated flash still shows the object's identity.
  • Cap the mix well below 1 (40–60% is plenty) for anything that can be retriggered rapidly.
  • Compare the two numbers directly when tuning: decay rate must exceed the worst-case event rate, or the effect must be designed to look correct while held on.
  • Where the flash must stay discrete, latch it for a fixed duration and ignore retriggers inside that window, rather than resetting the value.