LegacyShell WikiLegacyShell Wiki
Back to LegacyShell
Home
Wiki
Plugins
Docs
Back to LegacyShell
Home
Wiki
Plugins
Docs
  • Documentation

    • Getting Started

      • What is LegacyShell?
      • Speed Setup
      • Requirements
      • Installation
      • First Run
      • Config Files
      • Making an Account
      • Troubleshooting (Getting Started)
    • Running a Server

      • Architecture Overview
      • The Database
      • Users and Ranks
      • Adding Game Servers
      • Client Mirrors
      • Perpetual
      • Backups
      • Rate Limiting
      • Moderation
      • Closed Mode
      • Deployment
      • Troubleshooting (Running a Server)
      • Hosting for Someone Else's Instance
    • Content Creation

      • Maps
      • Dealing with Babylon Models
      • Map Blocks
      • Items and Skins
      • Hats and Stamps
      • Sounds
      • Gamemodes
      • Seasonal Events
    • Plugin Development

      • Quickstart
      • I Want To...
      • Anatomy of a Plugin
      • Lifecycle
      • Dependencies
      • Events (Concept)
      • Event Reference

        • services: events
        • game: events — shared logic (src/shell/)
        • game: events — main-thread server process
        • game: events — per-connection client object
        • game: events — room lifecycle & tick loop
        • game: events — in-browser gameplay
        • client: events — client server & build pipeline
      • Commands
      • Client-Side Code
      • Static Assets
      • Content Packs
      • Networking
      • Workers and State
      • Prediction and Authority
      • Recipes

        • Recipe: Killstreaks
        • Recipe: New Pickup Item
        • Recipe: New Gamemode
        • Recipe: Custom Weapon
        • Recipe: UI Modification
        • Recipe: Discord Integration
        • Recipe: Replacing Core Behaviour
        • Recipe: Persistent Plugin Storage
        • Recipe: Rewarding Players with Currency
        • Recipe: Custom Per-Player Data
        • Recipe: Custom Theme
      • Publishing
      • Pitfalls
      • Modifiers
      • Sound and Apollo
    • Codebase Reference

      • Repo Layout
      • Shared Shell Layer
      • Server-Only Markers
      • The ss Object
      • Build Pipeline
      • Stamps and Babylons
      • Game Loop
      • Rooms and Workers
      • Wire Protocol
      • Generated

        • Wire Protocol Opcodes
        • Enums & Lookup Tables
        • Database Schema
        • Config Reference
        • Slash Command Reference
      • Services Internals
      • Catalog and Items
      • Permissions Internals
      • Physics and Collision
      • Known Quirks
      • Codebase Anecdotes
      • Development Timeline
    • Contributing

      • Documentation Style Guide
      • Generators
      • For AI Agents

Prediction and Authority

Audience: Plugin authors · Prereqs: Commands

Canonical source: src/shell/player.js, src/shell/guns.js (the pattern, throughout)

You've already seen this pattern once, in Commands: a Command has both executeClient and executeServer, and only one runs depending on isClient. This isn't specific to commands - it's the core pattern the entire shared game-logic layer is built on, and understanding it is what separates a plugin that feels laggy from one that doesn't.

Why both halves exist

LegacyShell (like the original Shell Shockers, and most real-time multiplayer games) uses client-side prediction: when you press a key or fire a weapon, your own screen updates immediately, without waiting for a server round-trip - because waiting for that round trip on every single input would make the game feel unbearably laggy, even on a good connection. The server, meanwhile, is the actual authority - it doesn't trust the client's prediction, it independently simulates the same thing and is what every other player's view of you is actually based on.

This means most nontrivial gameplay code needs to answer "what happens right now, on my own screen" and "what actually happened, authoritatively" separately - sometimes with genuinely different logic (the client doesn't need to validate a command's permissions server-side-style; the server doesn't need to update DOM/UI elements).

The pattern in core code

You'll see isClient/isServer branches (from #isClientServer / #constants) inside the same function body throughout src/shell/, not split into separate files:

// simplified, illustrative of the real pattern in player.js/guns.js
someGameplayMethod() {
    if (isClient) {
        // update locally immediately - visuals, UI, local physics
    };

    if (isServer) {
        // the authoritative version - validate, apply, broadcast to other players
    };
};

player.js's core update() method, guns.js's fire(), and bullets.js's hit resolution all follow this - client does local prediction, server does authoritative resolution, and the client's local prediction gets silently corrected if it ever drifts from what the server says actually happened (via the state-buffer reconciliation described in Codebase Reference).

Applying this to your own plugin

If you're adding something a player directly triggers (a command, a custom weapon behavior, a UI reaction to their own input), you generally want both halves:

  • executeClient / the isClient branch: update whatever's visible immediately - don't wait for the server. This is what makes your feature feel as responsive as the base game.
  • executeServer / the isServer branch: the actual source of truth - validate input, apply the real state change, and (if other players need to see it) broadcast it, the same way core gameplay code does.

If you only implement the server half, your feature works but feels laggy compared to everything else in the game (a visible delay before anything happens). If you only implement the client half, it's not real - it won't be authoritative, won't be visible to other players, and a determined player could trivially fake it since nothing server-side is actually checking or enforcing it.

When you only need one half

Not everything needs both. A couple of legitimate exceptions:

  • Server-only effects with no client-predictable component - e.g. a moderation action like boot, or anything that only makes sense as "the server decided this," has no meaningful client-side prediction to do.
  • Purely cosmetic, non-competitive client-side behavior - a UI tweak, a sound effect, a visual flourish that never needs to be validated or seen by other players, doesn't need a server half at all, since there's nothing to cheat or desync.

The judgment call is whether the thing you're building affects gameplay other players experience (needs both) or is purely local/cosmetic (client-only is fine) or purely administrative (server-only is fine).

Next: Publishing, or skip ahead to the Recipes for complete worked examples that apply this pattern end to end.


This page was drafted with AI assistance and reviewed for accuracy. If something looks wrong, please open a PR or flag it.

Edit this page on GitHub
Prev
Workers and State