> For the complete documentation index, see [llms.txt](https://docs.allout.game/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.allout.game/scripting/networking-fundamentals.md).

# Networking Fundamentals

All Out is multiplayer-first, but CSL is designed so you can write gameplay almost like it’s singleplayer.

CSL runs that gameplay on the client for immediate feedback and on the server for authority. The client is regularly reconciled to the server's authoritative scene state. You do not write RPCs or separate network-spawn code.

## Write one gameplay path

Damage, rewards, spawning, movement, random choices, and sound calls should run in the shared gameplay path.

{% hint style="warning" %}
Do not guard gameplay with `Game.is_server()`. It disables prediction and can make client and server state diverge.
{% endhint %}

Keep per-player state on `Player` or another object owned by that player. Use scene-wide state only when it is truly shared.

## Player execution checks

`Player_Base` has two execution checks:

| Check                  | Use                                                                              |
| ---------------------- | -------------------------------------------------------------------------------- |
| `is_local_or_server()` | Player UI and the input it produces. Use it from that player's `ao_late_update`. |
| `is_local()`           | Player-specific visual overrides, such as hiding an object only from its owner.  |

All player UI, including cosmetic UI, belongs under `is_local_or_server()`. Never store gameplay or UI state only under `is_local()`; the next server sync replaces it.

```go
Player :: class : Player_Base {
    inventory_open: bool;

    ao_late_update :: method(dt: float) {
        if is_local_or_server() {
            rect := UI.get_safe_screen_rect()
                .top_right_rect()
                .grow(35, 90, 35, 90)
                .offset(-100, -45);
            bs := UI.default_button_settings();
            ts := UI.default_text_settings();

            if UI.button(rect, bs, ts, "Items").clicked {
                inventory_open = !inventory_open;
            }
        }

        if is_local() {
            // Apply player-specific visibility here, but do not change fields.
        }
    }
}
```

If a local visual override is changed by reconciliation, recompute it from synchronized state in `ao_on_state_sync()` or apply it every frame.

## Sound

Call `SFX.play` from the same shared gameplay path as the event. The engine deduplicates predicted playback when the server result arrives. To play a sound for one player, set `SFX_Desc.specific_to_player`; do not wrap the call in `is_local()`.

## Avoid per-frame state churn

Every gameplay field that changes can contribute to a network state diff. Do not store a countdown by subtracting `dt` from it every frame when the same state can be represented by a fixed time.

Store the transition time once and derive the remaining duration:

```go
Round_Timer :: class : Component {
    round_ends_at: float;

    begin_round :: method(duration: float) {
        round_ends_at = get_time() + duration;
    }

    remaining :: method() -> float {
        return max(0.0, round_ends_at - get_time());
    }

    ao_update :: method(dt: float) {
        if round_ends_at != 0.0 && get_time() >= round_ends_at {
            round_ends_at = 0.0;
            finish_round();
        }
    }
}
```

This synchronizes the deadline when it changes instead of synchronizing a new timer value every frame. The same pattern works for cooldowns, temporary effects, and scheduled state changes.

## Diagnosing mismatches

Check for:

* Gameplay hidden behind `is_local()` or `Game.is_server()`.
* Per-player values stored in globals.
* Local-only data used to seed gameplay randomness.
* State changed every frame when a fixed timestamp would represent it.

## Related docs

* [Adding Player Logic](/scripting/player-model.md)
* [Game/Frame Lifecycle](/scripting/game-frame-lifecycle.md)
