# Fixed step

> The game moves in steps of exactly 1/60 of a second whatever the screen, the way the consoles did: a 144 Hz monitor and a 60 Hz one play the very same game.

Example `fixed-step`, running at https://nacatamalon.com/en/examples/fixed-step. The whole catalogue: https://nacatamalon.com/llms/examples.md.

With `step: 60` in `createGame`, `delta` is always 1/60, a jump is the same height everywhere, and `x += 2` is two pixels a frame.

The bell hangs half a pixel below the top of the jump, so every jump rings it. Change `step: 60` to `step: 'display'` in the code and run it on a fast monitor: the jump comes out a pixel or two lower and misses, and the updates per second follow the screen.

## Code

`index.ts`:

```ts
import { createGame } from 'nacatamalon';
import { Level } from './scenes/demo';
import * as Settings from './settings';

const game = createGame('#app', {
    width: Settings.WIDTH,
    height: Settings.HEIGHT,
    step: 60,
    background: '#0d1020',
    actions: [
        {
            name: 'jump',
            bindings: [
                {
                    type: 'key',
                    key: ' '
                },
                {
                    type: 'button',
                    button: 'a'
                }
            ]
        }
    ],
});

// Declare Scenes
game({
    Level
});
```

`components/bell_challenge.ts`:

```ts
import { createSprite, createText, getColor, useActions, useUpdate } from 'nacatamalon';
import * as Settings from '../settings';

export const BellChallenge = () => {
    const actions = useActions();

    // The top of the jump, worked out the way the game moves: in steps of exactly 1/60.
    let apex = 0;

    for (let y = 0, vy = Settings.JUMP;vy > 0;) {
        y += vy / 60;
        vy -= Settings.GRAVITY / 60;
        apex = Math.max(apex, y);
    }

    const HERO = 16;
    const bellBottom = Settings.FLOOR - HERO - (apex - 0.5);

    createSprite({
        width: Settings.WIDTH,
        height: Settings.HEIGHT - Settings.FLOOR,
        tint: '#262d48',
        anchor: {
            x: 0,
            y: 0
        },
        transform: {
            x: 0,
            y: Settings.FLOOR
        }
    });
    createSprite({
        width: 2,
        height: bellBottom - 20,
        tint: '#4a5580',
        anchor: {
            x: 0.5,
            y: 0
        },
        transform: {
            x: 160,
            y: 0
        }
    });

    const bell = createSprite({
        width: 14,
        height: 12,
        tint: '#8a7a40',
        anchor: {
            x: 0.5,
            y: 1
        },
        transform: {
            x: 160,
            y: bellBottom
        }
    });

    const hero = createSprite({
        width: 12,
        height: HERO,
        tint: '#ffcc33',
        anchor: {
            x: 0.5,
            y: 1
        },
        transform: {
            x: 160,
            y: Settings.FLOOR
        }
    });

    const info = createText({
        text: '',
        transform: {
            x: 8,
            y: 8
        }
    });

    const top = createText({
        text: '',
        transform: {
            x: 8,
            y: 20
        },
        tint: '#9aa4c0'
    });

    const result = createText({
        text: 'PRESS SPACE / Ⓐ TO JUMP',
        transform: {
            x: 8,
            y: Settings.HEIGHT - 14
        },
        tint: '#6b7390'
    });

    let vy = 0;
    let height = 0;
    let highest = 0;
    let rang = false;
    let ring = 0;
    let steps = 0;
    let second = 0;
    let perSecond = 0;

    useUpdate((delta) => {
        // The updates of each second: 60 with a step, the screen's rate without one.
        steps += 1;
        second += delta;

        if (second >= 1) {
            perSecond = steps;
            steps = 0;
            second -= 1;
        }

        if (actions.justPressed('jump') && height === 0) {
            vy = Settings.JUMP;
            highest = 0;
            rang = false;
        }

        if (vy !== 0 || height > 0) {
            height += vy * delta;
            vy -= Settings.GRAVITY * delta;
            highest = Math.max(highest, height);

            // The head reaches the bell's bottom edge.
            if (!rang && Settings.FLOOR - HERO - height <= bellBottom) {
                rang = true;
                ring = 0.4;
            }

            if (height <= 0) {
                height = 0;
                vy = 0;
                result.text = rang ? 'RANG IT' : 'MISSED THE BELL';
            }
        }

        hero.transform.y = Settings.FLOOR - height;
        ring = Math.max(0, ring - delta);
        bell.tint = getColor(ring > 0 ? '#ffe680' : '#8a7a40');
        bell.transform.rotation = Math.sin(ring * 40) * ring;

        info.text = `UPDATES/S: ${perSecond}   DELTA: ${(delta * 1000).toFixed(2)} MS`;
        top.text = `TOP OF JUMP: ${highest.toFixed(2)} PX`;
    });
};
```

`scenes/demo.ts`:

```ts
import { createScene, useSpawn } from 'nacatamalon';
import { BellChallenge } from '../components/bell_challenge';

export const Level = () => {
    useSpawn(BellChallenge)();

    return createScene();
};
```

`settings.ts`:

```ts
export const WIDTH = 320;
export const HEIGHT = 180;
export const FLOOR = 150;
export const JUMP = 300;     // px/s up
export const GRAVITY = 900;  // px/s² down
```
