CHIP-8 keeps time with two registers that count down on their own at 60Hz. Today you add the delay and sound timers and the Tick that decrements them toward zero.
Add delay and sound timers that each decrement by one per tick, clamped at zero.
CHIP-8 has two timers, each a single byte that automatically counts down at 60Hz (sixty steps per second) independent of how fast instructions run. The delay timer is a general-purpose clock a program reads to pace itself; the sound timer makes the machine beep for as long as it is non-zero. Both work the same way: set them to a value, and they tick down to zero on their own.
Model the countdown as a Tick the host calls sixty times a second, separate from the instruction Step. Each Tick subtracts one from any timer that is above zero. The one edge to pin is the floor: a timer at zero must stay at zero, not wrap around to 255, so guard the decrement with a “greater than zero” check.
Keeping Tick distinct from Step is the whole point: the CPU runs many instructions per frame while the timers advance exactly once per frame, so running a Step must leave the timers alone. That decoupling is what makes CHIP-8 timing independent of how fast the interpreter executes. The opcodes that read and set these timers come next.
type VM struct {// ... existing fields ...delay, sound byte}func (v *VM) Tick() {if v.delay > 0 { v.delay-- }if v.sound > 0 { v.sound-- } // clamp at 0; never wrap to 255}