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

Shared Shell Layer

Audience: Core contributors, AI agents · Prereqs: Repo Layout

Canonical source: src/shell/, package.json's "imports" map, src/shell/general/misc.js (hashtagToString, hashtagToPath, prepareForClient)

The project's founding goal - "backend and clients share the same code" - is implemented by one mechanism: src/shell/*.js files are written once, then consumed two completely different ways depending on which runtime needs them.

Server-side: normal ESM imports, via #hashtag subpaths

package.json's "imports" field maps #bullets, #player, #catalog, #comm, #constants, and every other shared module to its real path under src/shell/:

"imports": {
    "#bullets": "./src/shell/bullets.js",
    "#player": "./src/shell/player.js",
    "#catalog": "./src/shell/catalog.js"
}

Any Node code (server-game, server-services, another shared module) just does import { Bullet } from '#bullets'; - Node resolves this exactly like a normal package import, no special handling needed. This is the entire mechanism server-side; there's no bundler, no transpilation step for these files when running under Node.

Browser-side: raw source text, spliced into one script

The browser never runs Node's module resolver. Instead, server-client/src/prepare-modified.js (the client build step - see Build Pipeline) reads each shared file's raw source text and textually inserts it into src/client-static/src/shellshock.min.js (a hand-maintained file that is, despite its name, not actually minified - it's the input to the build) in place of LEGACYSHELLXXX placeholder tokens.

The lookup goes through the same imports map, just resolved manually instead of by Node:

// misc.js
hashtagToPath: function (hashtag) {
    let fromJson = ss.packageJson.imports[hashtag];   // same package.json field Node uses
    return [path.join(ss.rootDir, fromJson.replace(".", "")), fromJson];
},
hashtagToString: function (hashtag) {
    const path = misc.hashtagToPath(hashtag);
    let file = fs.readFileSync(path[0], 'utf8');
    file = misc.prepareForClient(file);                // see below
    return file;
},

The four-step text transform (prepareForClient)

Before a shared file's text gets spliced into the bundle, misc.prepareForClient does exactly this, in order (the real implementation, four String.replaceAll calls):

prepareForClient: function (file) {
    file = `\n${file}`;
    file = file.replaceAll("\nimport ", "\n//(ignore) import ");
    file = file.replaceAll("\nexport default ", "\n//(ignore) export default ");
    file = file.replaceAll("\nexport ", "\n/*(ignore) export*/ ");
    file = file.replaceAll("\n//(server-only-start)", "\n/*(server-only-start)");
    file = file.replaceAll("\n//(server-only-end)", "\n(server-only-end)*/");
    return file;
},
  1. import lines get commented out. The concatenated bundle isn't an ES module, so a real import statement would be a syntax error there.
  2. export default gets commented out.
  3. export gets commented out (via a /*...*/ inline comment around just the keyword, not the whole line) - so a top-level export const Foo = ... becomes a plain const Foo = ..., which is a global in the concatenated script, visible to every other spliced-in file and to the rest of shellshock.min.js itself. This is the actual mechanism that lets Bullet, catalog, Comm, plugins, etc. all reference each other across file boundaries once flattened into one script - there's no browser-side import system standing in for what Node's module resolution does server-side.
  4. //(server-only-start)///(server-only-end) markers become a real block comment, deleting everything between them from the client build. See Server-Only Markers for the dedicated page on this convention.

Because of step 3, most nontrivial methods in player.js, bullets.js, guns.js, permissions.js branch internally on isClient/isServer (from #isClientServer) rather than being split into separate files per runtime - client does local prediction, server does authoritative resolution, in the same function body, reading the same globals either way.

What's actually in src/shell/

FileResponsibility
bullets.jsBullet/Rocket/Grenade projectile simulation and hit resolution.
guns.jsGun base class + the five weapon subclasses, fire logic.
munitionsManager.jsPer-room pooled management of active projectiles.
items.jsThe pickup-item array (AllItems/ItemTypes) - ammo, grenades, plugin-added pickups. See Catalog and Items.
itemManager.jsPer-room pooled management of spawned pickup items.
catalog.jsThe cosmetic/loadout shop catalog and weekly rotation algorithm. See Catalog and Items.
player.jsThe authoritative Player class - movement, combat, state-buffer prediction/reconciliation.
collider.jsVoxel-grid collision engine. See Physics and Collision.
math.jsMonkey-patches the global Math object with vector/angle/seeded-random helpers shared client/server.
pool.jsThe generic object-pool class everything high-frequency (bullets, items) is built on.
permissions.jsThe slash-command system. See Permissions Internals.
gametypes.jsGameTypes/defaultOptions - the gamemode and per-room gameOptions shape.
comm.jsThe binary wire protocol. See Wire Protocol.
events.jsThe seasonal event scheduler (EventManager) - unrelated to the plugin event system despite the name. See Seasonal Events.
constants.jsTick rate, enums, item-ID offset tables, iteratePlayers, and re-exports from isClientServer.js.
censor.jsChat profanity filtering.
stringWidth.jsText pixel-width measurement (Canvas-based, both sides).
loading.jsMap/mesh loading, including the block-naming-convention parser - see Map Blocks.
isClientServer.jsThe actual source of isClient/isServer/isEditor - the one primitive everything else branches on.
plugins.jsPluginManager itself - see Plugin Development.
general/misc.jsThe ss context object and the splice machinery described on this page. See The ss Object.
general/looper.jsThe server tick-loop scheduler. See Game Loop.
general/prepare-babylons.jsServer-only model-merging build step (not itself spliced into the client - it's never referenced via a LEGACYSHELLXXX token).
general/wsrequest.jsA minimal promise-wrapped WebSocket request helper, used for server-to-server calls.

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
Repo Layout
Next
Server-Only Markers