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

The ss Object

Audience: Core contributors, AI agents · Prereqs: Shared Shell Layer

Canonical source: src/shell/general/misc.js (instantiateSS)

ss is the shared server-side context/config bag every role builds at boot and keeps mutating throughout its lifetime. It only exists server-side - there is no client equivalent, and code that needs to run on both sides never references ss directly (client-side data gets baked into the bundle as separate globals via the LEGACYSHELLXXX placeholder substitutions instead - see Build Pipeline).

It's a mutable module-level binding, not a constant

// misc.js
export var ss; //trollage. access it later.

Genuinely var, genuinely reassigned (ss = {...}, then later ss = {...ss, config}, etc.) rather than mutated in place during instantiateSS itself - worth knowing if you're ever tempted to hold a reference to ss before instantiateSS runs and expect it to update; you'd be holding a reference to undefined, not a live binding that later gets filled in.

What instantiateSS(meta, argv, noStorage, noConfig) builds

Called once per process (and once per room worker thread - see Rooms and Workers) as the very first step of every role's boot sequence, before plugins even load:

FieldWhere it comes from
ss.currentDirThe resolved dirname of the calling module (meta.dirname from import.meta passed in by the entrypoint) - the specific role's own directory (e.g. server-game/).
ss.rootDirResolved as three .. up from src/shell/general/'s own location - the repo root, regardless of which role called it.
ss.configDeep merge: every store/config/*.yaml file over the matching src/defaultconfig/*.yaml default, with missing keys logged as warnings (up to 3 levels deep), then config.all's contents flattened into the top level and config.all itself deleted. Skipped entirely if noConfig is passed (used by init.js, which needs ss before store/config/ even exists yet).
ss.packageJsonThe parsed root package.json - this is how hashtagToPath/hashtagToString resolve #hashtag imports back to real file paths at runtime, reusing the exact same "imports" map Node itself uses.
ss.pluginsDir / ss.pluginsDirDefault<root>/plugins and <root>/plugins_default - consumed by PluginManager.loadPlugins.
ss.versionEnum / ss.versionHashRead from versionEnum.txt / versionHash.txt at the repo root (hash truncated to 7 characters).
ss.isPerpetualtrue if launched with --perpetual as the second CLI arg - see Perpetual.
ss.startTimeDate.now() at the moment instantiateSS ran.

The process exits immediately (process.exit(1)) if store/config/ doesn't exist yet (unless noConfig is set) - this is the actual mechanism behind "every server refuses to start without npm run init" from Installation.

What gets attached later, by other modules

ss keeps growing after instantiateSS returns - different roles bolt on different things as their own boot sequences progress:

  • Services (start-services.js): ss.db (the raw sqlite3.Database), ss.runQuery/ss.getOne/ss.getAll (promisified DB calls), ss.accs/ss.sess/ss.recs (the account/session/records-management modules), ss.requests_cache (the in-memory rate-limit cache - see Rate Limiting), ss.dbPath/ss.backupPath, and later ss.servicesSeed/ss.sqlPassword (lazily generated via misc.getServicesSeed/getSQLPassword, stored in the flags table).
  • Game (start-game.js): ss.RoomManager (the RoomManager instance), ss.thisServer (this game server's own identity/index once services responds to its requestConfig).
  • Game workers (worker.js): a much smaller seed - just {maps, items, permissions, config}, posted from the main thread via postMessage, and later ss.room (the live RoomConstructor instance for whichever room this worker owns) - see Rooms and Workers for why this is a genuinely separate, unshared ss per worker thread.
  • Client (start-client.js): ss.cache (raw JSON strings, not parsed objects, of items/maps/servers - kept as strings specifically so they can be spliced directly into the generated client bundle without a re-stringify step), ss.distributed_data/ss.distributed_config (the full merged config bundle from services, the latter YAML-dumped for admin display).

Practical implication: ss is a god-object, treat it as one

Because so many unrelated modules attach state to the same object over the process's lifetime, ss functions less like a typed config object and more like a shared namespace - convenient for cross-module access, but it means "what does ss contain right now" genuinely depends on when you ask, not just which role is asking. A plugin's constructor running early in boot sees a much sparser ss than the same plugin's event listener firing mid-game.


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
Server-Only Markers
Next
Build Pipeline