Skip to content

15 Input Bucket

In this chapter, we will add an input bucket to Note.

Input Buckets

An input bucket groups timing results from Notes. The result screen shows a judgment graph for each bucket.

While buckets are not required, they are very useful for helping players calibrate their input offset and improve their accuracy.

Define the Note Bucket

Our engine only has one type of note, so one bucket will do.

Each bucket is represented on the result screen by a graphic composed of skin sprites. Use Skin.note so the graphic matches the note's in-game appearance. Use milliseconds as the unit.

Update guide/lib/buckets.py, keeping note_window below Buckets:

python
from sonolus.script.bucket import Bucket, JudgmentWindow, bucket, bucket_sprite, buckets
from sonolus.script.interval import Interval
from sonolus.script.text import StandardText

from guide.lib.skin import Skin


@buckets
class Buckets:
    note: Bucket = bucket(
        sprites=[
            bucket_sprite(
                sprite=Skin.note,
                x=0,
                y=0,
                w=2,
                h=2,
                rotation=0,
            ),
        ],
        unit=StandardText.MILLISECOND_UNIT,
    )


note_window = JudgmentWindow(
    perfect=Interval(-0.05, 0.05),
    great=Interval(-0.1, 0.1),
    good=Interval(-0.2, 0.2),
)

The template already passes Buckets to the play mode, so defining the bucket is all the mode setup required.

Reuse the Judgment Windows

The note_window that we added earlier now serves two purposes: Note uses it to judge input, and the result screen uses the same timing boundaries for its judgment graph. Copy those boundaries into the bucket once. Do not put this setup in Note.preprocess, which runs once for every Note entity.

One-Time Setup

Add init_buckets below note_window in guide/lib/buckets.py. The bucket uses milliseconds, so multiply the judgment window by 1000. The @= operator copies every field from the value on its right into the existing value on its left:

python
def init_buckets():
    Buckets.note.window @= note_window * 1000

Now open guide/play/initialization.py and call init_buckets from Initialization.preprocess:

python
from guide.lib.buckets import init_buckets


class Initialization(PlayArchetype):
    def preprocess(self):
        # ...

        init_buckets()

        # ...

The bucket setup runs once because the level contains one Initialization entity.

Assign the Bucket to Each Result

Lastly, update Note.touch in guide/play/note.py after setting self.result.accuracy. Assign the Note bucket and store the same timing difference in milliseconds:

python
from guide.lib.buckets import Buckets, note_window


class Note(PlayArchetype):
    # ...

    @callback(order=1)
    def touch(self):
        # ...

        for touch in touches():
            # ...

            self.result.bucket @= Buckets.note
            self.result.bucket_value = self.result.accuracy * 1000

            # ...

Checkpoint

After completing a play session, open the Note judgment graph on the result screen. Hit markers should be positioned by their early or late timing in milliseconds.