Node/Web implementation of an Ultima Online shard and browser client. The project started as a functional migration from ServUO and ClassicUO patterns, but the goal is an independent codebase: compatible with the UO protocol where that is useful, and designed for Node.js, WebSocket, PixiJS, and modern browser rendering.
This repository does not include game files. You generate runtime assets locally from your own legal Ultima Online Classic installation.
This project is prepared for public source distribution.
- Project code is licensed as
AGPL-3.0-or-later. - Modified versions that are distributed or offered as a network service must make the corresponding source code available to the community under the same license terms.
- The repository must not contain Ultima Online client files, generated assets, world saves, accounts, passwords, shard secrets, or private player data.
- Contribution rules are documented in
CONTRIBUTING.md. - The full license text is in
LICENSE.
| Area | Technology | Role |
|---|---|---|
| Server | Node.js, ESM, ws |
Shard runtime, world state, accounts, persistence, scripts, gameplay systems |
| Client | Vite, PixiJS v8, WebGL | Browser UO client for the project WebSocket flow and bridge flow |
| Scripts | JavaScript ESM + Script API | Commands, items, mobiles, NPCs, AI, skills, spells, quests, spawns |
| Extractor | Node.js + sharp |
MUL/UOP to PNG/JSON/bin assets, optional KTX2/Basis output |
| Control Panel | Electron | Start/stop server, client, bridge, extractor, tools, and logs |
| Bridge | Node.js TCP/WebSocket | Browser client to raw TCP ServUO/RunUO/OSI-style shard |
The priority is functional coverage against ServUO and ClassicUO behavior without copying their architecture one-to-one. Reference projects are used for parity, protocol behavior, and bug fixing; the runtime design should stay native to this repository.
Main rules:
apps/serverowns the engine and world indexes.apps/scriptsowns gameplay content.- Scripts should use high-level
api.game,api.lifecycle,api.systems,_inventory,_movement,_spatial, and registry helpers. - The client preserves UO behavior, but rendering, cache, UI, and the asset pipeline are built for the web.
- PNG assets are always the fallback path.
.ktx2is an optional faster path for VRAM/decode if you generate it withtoktx. templates/is reference material for audits and bug fixes, not runtime code.
apps/
client/ Vite + PixiJS browser client
server/ shard, world engine, net handlers, persistence, systems
scripts/ gameplay content and server scripting API consumers
bridge/ WebSocket <-> raw TCP bridge for external shards
control-panel/ Electron launcher with logs and tool buttons
packages/
protocol/ UO binary protocol, packets, Huffman/shared helpers
extractor/ asset pipeline: MUL/UOP -> client assets
tools/
run-control-panel.bat main Windows GUI launcher
run-control-panel.sh main Linux/macOS GUI launcher
bats/ Windows command launchers
sh/ Linux/macOS command launchers
audit/ ServUO/ClassicUO audits and coverage maps
docs/
README.md documentation index
server-scripting.md guide for the current server Script API
templates/
ClassicUO/ServUO reference code, for comparison only
saves/
local world/account saves
- Node.js
>=20.11 - pnpm
9.12.0through Corepack or a global install - Windows
.batlaunchers or Linux/macOS.shlaunchers - A local Ultima Online Classic installation for asset extraction
- Optional Khronos KTX-Software (
toktx) for.ktx2generation
Basic check:
node -v
corepack enable
pnpm -vRecommended Windows path:
tools\run-control-panel.batRecommended Linux/macOS path:
chmod +x tools/run-control-panel.sh tools/sh/*.sh
tools/run-control-panel.shThe Control Panel can:
- install the dependencies needed to start the Electron launcher;
- start the server, browser client, bridge, admin panel, and extractor;
- run project install/update actions;
- check and install KTX2/
toktxtooling; - stream service logs in one window.
Terminal path for Linux/macOS:
pnpm install
UO_SRC="/path/to/Ultima Online Classic" tools/sh/extract-assets.sh
tools/sh/run-server.sh
tools/sh/run-client.shTerminal path for Windows:
pnpm install
$env:UO_SRC="C:\Program Files (x86)\Electronic Arts\Ultima Online Classic"
tools\bats\extract-assets.bat
tools\bats\run-server.bat
tools\bats\run-client.batThe development client normally runs at:
http://localhost:5173
The project WebSocket server normally runs at:
ws://127.0.0.1:2593/game
A full extraction creates:
apps/client/public/assets/
That directory contains art/gump/anim/static atlases, map chunks, statics, tiledata, hues, multi data, lights, fonts, cliloc, and manifests.
Common commands:
pnpm extract
pnpm extract:ktx2:tool:check
pnpm extract:ktx2:tool:install
pnpm extract:ktx2pnpm extract generates PNG/JSON/bin fallback assets. pnpm extract:ktx2
converts existing PNG atlases to .ktx2 when toktx is available. The client
tries KTX2 where present and falls back to PNG when the parser or file is not
available.
KTX2 is mainly useful for large texture atlases: smaller transfer, lower VRAM use, and faster GPU upload after transcoding. PNG remains the required fallback and the easiest format for debugging.
| Scenario | Windows | Linux/macOS |
|---|---|---|
| GUI launcher | tools\run-control-panel.bat |
tools/run-control-panel.sh |
| Browser client + project server | tools\bats\run-all.bat |
tools/sh/run-all.sh |
| WebSocket server only | tools\bats\run-server.bat |
tools/sh/run-server.sh |
| WebSocket server + raw TCP for ClassicUO | tools\bats\run-server-tcp.bat |
tools/sh/run-server-tcp.sh |
| WebSocket server + TCP + admin panel | tools\bats\run-server-admin.bat |
tools/sh/run-server-admin.sh |
| Vite client only | tools\bats\run-client.bat |
tools/sh/run-client.sh |
| Browser client through bridge to an external shard | tools\bats\run-client-bridge.bat host:port |
tools/sh/run-client-bridge.sh host:port |
| Bridge only | tools\bats\run-bridge.bat host:port |
tools/sh/run-bridge.sh host:port |
Default ports:
| Service | Port |
|---|---|
| Browser client Vite | 5173 |
| Project WebSocket server | 2593 |
| Raw TCP listener | 2594 |
| Bridge | 2595 |
| Admin panel | 2596 |
| Command | Purpose |
|---|---|
pnpm install |
install monorepo dependencies |
pnpm server |
start the server with node --watch |
pnpm client |
start the Vite dev server |
pnpm build |
build workspaces that define a build script |
pnpm test |
run workspace tests |
pnpm lint |
run ESLint for the repository |
pnpm extract |
extract assets from UO MUL/UOP files |
pnpm extract:ktx2 |
generate .ktx2 files next to PNG atlases |
pnpm client:perf-smoke |
run the client performance smoke test |
pnpm client:profile |
capture a client profile |
pnpm audit:servuo |
audit server functionality against ServUO |
pnpm audit:servuo:map:check |
check the ServUO coverage map |
apps/server is the runtime engine:
- accounts, sessions, and login;
- WebSocket and optional raw TCP listener;
- UO binary packets and protocol compatibility;
- world state: mobiles, items, sectors, parent/child indexes, persistence;
- movement, visibility, update ranges, pathfinding, and standing Z;
- combat, notoriety, poison, regen, party, guild, chat, corpses;
- systems dispatch: spells, crafting, housing, pets, PvP, rewards, bosses, quests, economy, events;
- admin panel and diagnostics.
Architectural rule: the engine should not contain concrete game content when
that content can live in apps/scripts. The engine exposes APIs, indexes, and
safe operations; scripts register content and behavior.
apps/scripts/src is the gameplay layer:
- player and admin commands;
- item definitions and item lifecycle scripts;
- NPC/mobile templates, vendors, and AI;
- skills, spells, crafting, loot tables;
- quests, regions, spawns, and events;
- JSON data in
apps/scripts/src/data.
The canonical guide for the current API is:
docs/server-scripting.md
The old pattern of scanning world.items or world.mobiles directly is an
exception. New code should use:
api.gamefor gameplay operations and indexed reads;api.lifecyclefor hot-reload-safe commands, events, and timers;api.systemsfor engine domains;_inventory.js,_movement.js,_spatial.js, and_entities.js;- declarative
defineScript()for simple modules.
apps/client is a web implementation of the UO client:
- Vite + PixiJS v8;
- isometric WebGL renderer;
- map streaming and chunk cache;
- PNG atlases with optional KTX2/Basis;
- mobile/static animations, gumps, paperdoll, journal, macro/hotkey flow;
- dynamic lights from world and equipment;
- roof/ceiling autohide for buildings;
- UI that follows ClassicUO behavior while using DOM/WebGL-friendly patterns;
- smoke tests for protocol, UI, rendering, pathfinder, asset cache, lights, gumps, animation, and performance budgets.
Useful checks after renderer changes:
pnpm --filter @uo/client run smoke:roof
pnpm --filter @uo/client run smoke:light
pnpm --filter @uo/client buildFull client smoke:
pnpm --filter @uo/client run smokeThe project supports three practical configurations:
browser client -> project WebSocket server
ClassicUO/Razor -> project raw TCP server
browser client -> WebSocket bridge -> external ServUO/RunUO raw TCP shard
This lets the browser client evolve without abandoning protocol compatibility, and it lets the server be tested with native clients.
Local saves live in:
saves/world.json
saves/accounts.json
Locally generated assets live in:
apps/client/public/assets/
These paths are environment data. Do not treat them as source-of-truth review material unless you are intentionally testing persistence migration or the asset pipeline.
| Document | Role |
|---|---|
QUICKSTART.md |
short setup and launch guide |
CONTRIBUTING.md |
contribution, testing, and licensing rules |
LICENSE |
full AGPL license text |
CODE_OF_CONDUCT.md |
behavior rules for the public project |
docs/README.md |
repository documentation index |
docs/server-scripting.md |
current server scripting rules |
tools/README.md |
launchers, Control Panel, bridge, and environment variables |
apps/server/src/content/ARCHITECTURE.md |
engine vs scripts split |
apps/server/src/systems/README.md |
systems layer overview |
Before a larger change:
git status --short
pnpm testAfter server changes:
pnpm --filter @uo/server testAfter client changes:
pnpm --filter @uo/client build
pnpm --filter @uo/client run smoke:client-perfAfter script changes:
pnpm --filter @uo/server test
pnpm audit:servuo:map:checkAfter asset pipeline changes:
pnpm extract:ktx2:tool:check
pnpm --filter @uo/extractor run ktx2:dry-runBefore opening a pull request:
- read
CONTRIBUTING.md; - explain what changed and why;
- include tests or manual verification;
- keep refactors, features, and data changes separate where practical;
- do not add generated assets, world saves, or private data;
- make sure the contribution can be licensed as
AGPL-3.0-or-later.
- Do not migrate scripts back to the old raw runtime API.
- Do not scan
world.itemsorworld.mobilesin hot paths when an indexed facade exists. - Do not assign
item.parentormob.x/y/z/mapdirectly from gameplay code; useapi.game.item.move,api.game.mobile.move, orapi.game.mobile.teleport. - Script registrations should be hot-reload-safe through
api.lifecycle. - The client should preserve UO behavior, but implementation can be web-native: batching, caches, atlases, lazy loading, dirty regions, and fixed-row virtual lists are preferred.
templates/is for audits, comparisons, and bug fixing; it is not runtime code.
Project code is licensed as AGPL-3.0-or-later. The intent is simple: you may
use, copy, host, and modify the project, but project changes should return to
the community under the same terms when distributed or offered over a network.
This repository does not distribute Ultima Online files. Asset extraction requires your own legal copy of the UO client. Original code in this repository is a separate implementation; for any files ported from or based on external projects, respect the licenses noted in headers, history, and documentation.