{"id":50422368,"url":"https://github.com/xrip/uo-client","last_synced_at":"2026-06-01T10:00:45.686Z","repository":{"id":360488332,"uuid":"1250108783","full_name":"xrip/uo-client","owner":"xrip","description":"Ultima Online T2A client recreated from Origin's 2.0.7 client decompilation","archived":false,"fork":false,"pushed_at":"2026-05-31T08:05:32.000Z","size":5874,"stargazers_count":13,"open_issues_count":0,"forks_count":3,"subscribers_count":1,"default_branch":"master","last_synced_at":"2026-05-31T09:22:19.772Z","etag":null,"topics":["bot","decompilation","ida","ultima-online","uo"],"latest_commit_sha":null,"homepage":"","language":"C++","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/xrip.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-05-26T10:07:43.000Z","updated_at":"2026-05-31T09:06:30.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/xrip/uo-client","commit_stats":null,"previous_names":["xrip/uo-client"],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/xrip/uo-client","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xrip%2Fuo-client","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xrip%2Fuo-client/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xrip%2Fuo-client/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xrip%2Fuo-client/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/xrip","download_url":"https://codeload.github.com/xrip/uo-client/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xrip%2Fuo-client/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33769492,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-01T02:00:06.963Z","response_time":115,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["bot","decompilation","ida","ultima-online","uo"],"created_at":"2026-05-31T09:01:01.101Z","updated_at":"2026-06-01T10:00:45.661Z","avatar_url":"https://github.com/xrip.png","language":"C++","funding_links":[],"categories":[],"sub_categories":[],"readme":"# uo-client\n\nA from-scratch **Ultima Online (T2A, protocol 2.0.7)** client written in C++17,\nbuilt as the engine for an **automation / bot framework** with a **graphical\nfrontend that recreates the look of the original 1997-era client**.\n\nThe point is not to give you another way to *play* UO by hand. The point is to\nlet a bot play — pathfind, follow, open doors, react to obstacles — while you\n**watch it happen** in a faithful isometric window and step in when you want to.\nThis is the classic UO *babysitting* loop: the script grinds, you keep an eye on\nit. Here the script is a real protocol client and the window is a software\nreimplementation of the original renderer.\n\nAnd the \"script\" can be a **real script**: an embedded JavaScript engine lets you\nwrite the bot's high-level behaviour in JS on top of the C++ navigation core. The\none that ships — a **lumberjack** — chops trees, banks the logs, restocks\nconsumables from vendors, eats, and fights, flees or resurrects on its own.\n\n## Demo\n\n[![Watch the bot navigate in the isometric window](https://img.youtube.com/vi/0YYXLrZHQfE/maxresdefault.jpg)](https://www.youtube.com/watch?v=0YYXLrZHQfE)\n\n*The A\\* bot pathfinding across Britannia while the software renderer recreates the 1997-era client — click to watch on YouTube.*\n\n\u003e **Scope.** This client talks to one server: the reverse-engineered **UO Demo**\n\u003e shard at **[draxinar/ouo](https://github.com/draxinar/ouo)**. Compatibility with\n\u003e other shards (OSI, modern emulators) is **out of scope** and not planned.\n\n---\n\n## Table of contents\n\n- [Demo](#demo)\n- [What it is / what it is not](#what-it-is--what-it-is-not)\n- [How it was built — LLMs + reverse engineering](#how-it-was-built--llms--reverse-engineering)\n- [Architecture](#architecture)\n- [Networking \u0026 protocol](#networking--protocol)\n- [Movement \u0026 the navigation bot](#movement--the-navigation-bot)\n- [Bot scripting (JavaScript)](#bot-scripting-javascript)\n- [Renderer — the observation frontend](#renderer--the-observation-frontend)\n- [The target server](#the-target-server)\n- [Requirements \u0026 game assets](#requirements--game-assets)\n- [Build](#build)\n- [Run \u0026 configuration](#run--configuration)\n- [Commands \u0026 window controls](#commands--window-controls)\n- [Testing \u0026 regression harnesses](#testing--regression-harnesses)\n- [Project layout](#project-layout)\n- [Status, limitations \u0026 roadmap](#status-limitations--roadmap)\n- [Developer documentation](#developer-documentation)\n- [License \u0026 disclaimer](#license--disclaimer)\n\n---\n\n## What it is / what it is not\n\n**It is:**\n\n- A complete **2.0.7 protocol client** — login handshake, Huffman-compressed game\n  stream, packet framing, movement, mobiles, items, stats, speech/journal.\n- An **A\\* navigation bot** that drives a character with predict-and-reconcile\n  movement, door handling, dynamic obstacle avoidance, and follow logic.\n- An **embedded JavaScript scripting layer** for writing bots as priority\n  behaviours (cancellable async steps) with batteries-included banking, survival\n  and vendor-restock skills — shipped with a full autonomous **lumberjack**.\n- A **software isometric renderer** that draws land, statics, dynamic items,\n  animated mobiles (with equipment, mounts, hues and night lighting), a radar\n  minimap and a HUD — modelled directly on the original client's output.\n\n**It is not:**\n\n- A hand-playable game client. Manual controls exist (arrow-key walk, click-to-go,\n  war/peace toggle) but only as *supervision* tools layered over the bot.\n- A general-purpose UO client. It implements exactly what the target server speaks\n  and is verified against that one client/server pair.\n- A cheat for live/official shards. It targets a private, reverse-engineered\n  research server and ships no game assets.\n\n**Design goals, in priority order:**\n\n1. **Automate flexibly.** The client core (`Client`) owns protocol state and feeds\n   a navigation layer that any higher-level behaviour can drive.\n2. **Observe faithfully.** The frontend should look and behave like the real 2.0.7\n   client so you can supervise the bot the way you'd babysit a macro in-game.\n3. **Stay correct by construction.** Behaviour is continuously checked against the\n   decompiled original; the decompilation itself is treated as a living artifact.\n\n---\n\n## How it was built — LLMs + reverse engineering\n\nThis project is an experiment in **LLM-driven systems development**. Both the\napplication code **and** the reverse-engineering of the original client are done\n**exclusively through large language models** — primarily **Codex 5.5** and\n**Claude Opus 4.7**. There is no hand-written C++ baseline underneath; the LLMs\nread the decompiled binary, form hypotheses, write the client, and verify the\nresult against the running original.\n\nThe methodology that keeps this honest:\n\n- **The decompiled `client_2.0.7.exe` is the source of truth.** Wherever behaviour\n  is non-obvious, the implementation cites the original by address or symbol. The\n  source is dense with references such as `Network_ProcessBuffer @ 0x42D8E0`\n  (Huffman / packet buffering), `CRadarGump_Update` / `CRadarGump_RenderMinimap`\n  (the radar minimap rule), and `g_SittingChairTable @ 0x55DB68` (chair seating) —\n  **43 decompiled-client references across 10 source files** at last count.\n- **The decompilation is a living artifact.** Reverse-engineering happens in IDA,\n  and every newly confirmed behaviour — plus every correction to a bad decompiler\n  guess — is written **back into the IDB** as comments, renamed functions, types\n  and variables, then saved. The local C++ source is never allowed to become the\n  *only* record of a 2.0.7 finding.\n- **Continuous verification.** Renderer changes are diffed against the official\n  client visually (see [regression harnesses](#testing--regression-harnesses));\n  protocol and pathfinding changes are checked against deterministic probes. When\n  the model's port of a client routine diverges from the binary's behaviour, the\n  binary wins and the code (and the IDB) are corrected.\n\nThe result is a codebase whose comments double as a reverse-engineering logbook:\nreading `src/render/Renderer.cpp` or `src/net/Huffman.cpp` tells you not just what\nthe code does, but *which part of the original it mirrors and why*.\n\n---\n\n## Architecture\n\nThe code is C++17 written in a deliberate **\"C with Classes\"** style: no\nexceptions, no RTTI (`/EHs-c- /GR-`), plain structs, explicit ownership via\n`std::unique_ptr`, fixed-width type aliases (`u8`/`i32`/`usize` from\n`include/uo/types.h`), `PascalCase` types and `lowerCamelCase` fields. There is no\nheavy template metaprogramming and no hidden allocation in the hot paths.\n\n```\n                         ┌──────────────────────────────────────────┐\n                         │                 Client                     │\n                         │  protocol state machine · packet dispatch  │\n                         │  movement · bot commands · render ticks    │\n                         └───────┬───────────────┬───────────────┬────┘\n                                 │               │               │\n                ┌────────────────┘               │               └─────────────┐\n                ▼                                 ▼                             ▼\n        ┌───────────────┐                ┌─────────────────┐           ┌────────────────┐\n        │      net      │                │   navigation    │           │     render     │\n        │ Socket        │                │ PathPlanner     │           │ Renderer (iso) │\n        │ PacketStream  │                │  (worker thread)│           │ Minimap/Radar  │\n        │ Huffman       │                │ NavigationState │           │ Text / HUD     │\n        └───────────────┘                │ + bot/ (A*,     │           │ MiniFBWindow   │\n        ┌───────────────┐                │   Blacklist)    │           └────────┬───────┘\n        │   builders    │                └────────┬────────┘                    │\n        │ outbound pkts │                         │                             │\n        └───────────────┘                         ▼                             ▼\n                                          ┌─────────────────────────────────────────┐\n                                          │                  mul                      │\n                                          │  Map · TileData · World (walkability)     │\n                                          │  Art · Texmap · Anim · AnimData · Hues    │\n                                          │  Verdata · RadarColors                    │\n                                          └─────────────────────────────────────────┘\n```\n\n| Module | Responsibility |\n|---|---|\n| `Client` (`src/Client.{h,cpp}`, `src/client/ClientRender.cpp`) | Connection state machine, packet dispatch, player/mobile/item caches, bot command surface, per-tick render driver. ~1.8k LOC of dispatch + glue. |\n| `net/` | `Socket` (winsock wrapper), `PacketStream` (length-table framing), `Huffman` (server→client game-stream decompression). |\n| `builders/` | Outbound packet construction (seed, move, speech, double/single click, OpenDoor, login, etc.). |\n| `navigation/` | `PathPlanner` runs A\\* on a **background worker thread** (request/poll); `NavigationState` holds movement, bot route, follow and learned-blocker state. |\n| `bot/` | `Pathfinding` (the A\\* core + grass bias) and `Blacklist` (runtime walkability overlay + `blacklist.mul` verdata I/O). |\n| `js/` (`src/js/`) | Embedded **QuickJS** engine (`JsEngine`) plus `Player` / `World` / `Mobiles` / `Vendor` script bindings (`ClientBindings`). Runs the bot scripts under `scripts/js/`. |\n| `mul/` | MUL/verdata asset loaders and `World::QueryCell` walkability. Also builds `uo_mul.lib` and the `uo_mul_dump` CLI. |\n| `render/` | Software isometric `Renderer` (ARGB1555), `Minimap`/`RadarColors`, `Text`/HUD, and the `MiniFBWindow` host. |\n\n---\n\n## Networking \u0026 protocol\n\nA single TCP socket using the **\"stay-on-socket\"** model. The login → in-world\nflow is:\n\n```\nseed → 0x80 → 0xA8 → 0xA0 → 0x8C → (seed) 0x91 → 0xB9 → 0xA9 → 0x5D → 0x1B → 0x55\n```\n\n- **No encryption.** The server runs in *nocrypt* mode; the 4-byte plaintext seed\n  is just a relay token (the default `0xAC1CA001` is literally the server IP).\n- **Huffman decompression** (`src/net/Huffman.*`). The server begins compressing\n  the game stream the moment it processes our `0x91` game-login; everything from\n  `0xB9` onward is compressed. The decoder builds its trie from the **same table\n  the server compresses with** (so the two can't drift), walks it MSB-first, and on\n  the flush marker `256` discards the rest of the current byte (per-packet byte\n  alignment) — matching the original client's `Network_ProcessBuffer @ 0x42D8E0`.\n- **Framing** uses the `g_PacketLengthTable` parity rules in\n  `include/uo/packet_lengths.h` (fixed-length vs. self-describing packets).\n\n**Handled inbound packets** include: `0x11` stats, `0x1A` object, `0x1B`\nlogin-confirm, `0x1C`/`0xAE` ASCII/Unicode messages, `0x1D` delete, `0x20`\ndraw-player, `0x21`/`0x22` move reject/ack, `0x2D` mob attributes, `0x2E` equip,\n`0x3A` skills, `0x4E`/`0x4F` light levels, `0x55` login-complete, `0x6E`\nanimation, `0x72` war-mode, `0x73` ping, `0x77`/`0x78` mobile move/incoming,\n`0x81`/`0xA9` char lists, `0x82` login-denied, `0x8C` connect-to-gameserver,\n`0x98` mob name, `0xA1`/`0xA2`/`0xA3` HP/mana/stamina, `0xA8` server-list, `0xAF`\ndeath, `0xB9` features, `0xBD` version-query, `0xC8` view-range.\n\nItem, container and vendor flows add `0x24` container gump, `0x3C` container\ncontents, `0x74` vendor shop data, `0x3B` vendor offer, `0x88` paperdoll (the only\nclient-visible carrier of an NPC's job title), `0x2C` resurrect menu and `0x7C`\nserver menu/dialog.\n\n**Outbound builders** (`src/builders/Builders.cpp`): seed, `0x02` move, `0x03`\nspeech, `0x06` double-click, `0x09` single-click, `0x12` OpenDoor (subcommand\n`0x58`), `0x5D` play-character, `0x73` ping, `0x80` login, `0x91` game-login,\n`0xA0` select-server, `0xBD` version.\n\nThe client also sends `0x07`/`0x08` (lift / drop), `0x34` (status query, for mob\nHP), `0x3B` (vendor buy) and `0x98` (all-names query) for the item, vendor and\ncombat interactions used by the commands and scripts.\n\nEvery packet is logged to a JSONL file and to the console (gated by a `verbose`\ntoggle so the window isn't drowned in per-tick chatter).\n\n---\n\n## Movement \u0026 the navigation bot\n\n### Predict-and-reconcile movement\n\nMovement is **pipelined** (`kMaxInFlight = 4`, the \"fastwalk stack\"): several\n`0x02` moves may be in flight at once, each predicting its new position/facing\nlocally, then reconciled on the server's replies.\n\n- The `0x22` **ack carries no position**, but we never need it — position is\n  predicted locally and only ever corrected by a reject. Pipelining is safe\n  because the `0x21` reject carries the authoritative pose (see below) and the\n  server has no step-rate anti-speedhack (the throttle still paces *sends*).\n- `0x21` **reject** snaps the client back to the server's authoritative pose and\n  drops the in-flight queue. A blocked step makes the server deny every queued\n  move behind it (it holds MovePrevented until we resend `seq 0`), so a depth-N\n  pipeline yields N identical rejects; only the first (whose seq is still\n  in-flight) is acted on, the rest just resync the pose. Steps that were\n  speculatively consumed from the path are restored so a reroute/door-retry\n  resumes from the right spot.\n- `0x20` is a full resync that aborts the current path.\n- **Turn-then-step:** stepping a new direction first turns (an acked server\n  `DoTurn`) and then steps; both are predicted locally.\n- **Cadence:** canonical foot speeds — **run 200 ms / walk 400 ms** per step. The\n  server has no step-timing anti-speedhack, so pacing is purely for realism.\n- A **5 s watchdog** aborts the path if the oldest in-flight move is never acked.\n\n### Threaded A\\* planner\n\n`navigation::PathPlanner` runs the A\\* search on a **dedicated worker thread**. The\nclient posts a `PathRequest` (start, goal, blacklist, live mobiles, dynamic items)\nand later `Poll()`s for a `PathResult` — so a long cross-continent search never\nstalls the render loop or the network pump.\n\nThe search itself (`bot/Pathfinding`) is 8-connected A\\* over\n`World::QueryCell`:\n\n- Costs **10** (straight) / **14** (diagonal); admissible Chebyshev heuristic.\n- **No corner-cutting:** a diagonal step requires both orthogonal neighbours walkable.\n- Step limits `maxStepUp/Down = 12`, `charHeight = 16`; node cap `32768`.\n- **Grass penalty** biases routes onto roads/dirt/cobble (where mobs are sparser)\n  while keeping the heuristic admissible.\n- A **blacklist overlay** is consulted after the MUL walkability checks.\n\n### Lookahead patching\n\nRather than rebuild the whole route every tick, the bot previews the next few\nsteps of the existing path, flags any cell newly blocked by the transient\nblacklist / a fresh mobile (`0x77`/`0x78`) / a blocking dynamic item (`0x1A`) /\nplain unwalkable terrain, and tries a small, cheap A\\* **patch** around the\nblockage — splicing it into the path prefix and keeping the tail. Full replanning\nis the fallback.\n\n### Obstacle, door, mobile \u0026 fatigue handling\n\nOn a `0x21` reject the bot decides, **in order**:\n\n0. **Fatigue (stamina).** A reject shortly after a *\"too fatigued to move\"*\n   message is treated as spent stamina — wait for regen and retry, **never**\n   blacklist.\n1. **Mobile on the tile.** A cached mobile on the blocked cell is a moving/shove\n   obstacle — wait briefly and retry, **never** blacklist.\n2. **Door.** Send the legitimate **OpenDoor action** (`0x12`/`0x58`); the server\n   spatially searches the faced tile and opens any door there (graphic- and\n   timing-independent). Confirm via the resulting `0x1A` update, retry, and a cell\n   with a known door is **never** blacklisted.\n3. **Wall / lamp post / unknown static.** Only now add a **transient** (this-trip)\n   avoid and reroute.\n\n`blacklist.mul` I/O exists (verdata format) but **auto-persist is disabled** —\nthe bot uses transient avoidance only, so it can't poison real passages.\n\n---\n\n## Bot scripting (JavaScript)\n\nAbove the C++ navigation core sits an embedded\n**[QuickJS](https://bellard.org/quickjs/)** engine (`src/js/`), so the bot's\n*high-level* behaviour is written in JavaScript — no recompile. Edit a script, type\n`run` again, and it reloads in a fresh runtime; script errors are caught and\nprinted (`[js]`) and never crash the client.\n\n```\nrun scripts\\js\\lumberjack.js      :: load + run a bot script (re-run to reload)\njs stop                           :: tear the running script down\n```\n\n**Scripting surface** (`src/js/ClientBindings.cpp`): `Player` (live state +\nactions — goto, use, equip, attack, follow, say, drop, `requestStatus`, …),\n`World` (`statics`, the stump overlay), `Mobiles` (live serial-backed handles\ncarrying HP / notoriety / body / paperdoll title), and `Vendor` (speech-triggered\nbuying). Events (`on`/`once`) surface journal lines, target cursors, arrivals,\ncontainer opens, incoming/leaving mobiles, attacks, dialogs, resurrect menus,\npaperdolls and vendor windows. `scripts/js/globals.d.ts` is the typed source of\ntruth for the whole surface.\n\n**The behaviour runner** (`scripts/js/lib/bt.js`): a bot is a flat list of\nbehaviours in **priority order**. Each tick the highest-priority behaviour whose\nguard is true owns the body, and a strictly higher-priority one **preempts** it.\nPreemption is cooperative via a **cancellation token**: long awaits are wrapped so\na preempted step unwinds at once (a threat interrupts chopping within a tick, not\nafter the 15-second chop wait). A `BehaviorScript` base class (`lib/bot.js`) adds\nthe lifecycle (incl. an awaited one-time `onStartup`), movement (`walkTo`) and\ninventory; opt-in mixins add banking (`lib/bank.js`) and survival/restock\n(`lib/survival.js`). A shared **threat meter** (`lib/threat.js`) scores nearby\ndanger from a body-list of aggressive creatures plus confirmed-attack signals.\n\n**The shipped bot** — `scripts/js/lumberjack.js` — is a complete worked example:\nit rotates between forest stands chopping trees, banks the logs when full, withdraws\ngold, restocks bandages/food from vendors (matched by paperdoll *job* title, not\nname), eats on a timer, and — driven by the threat meter — fights, bandages\nmid-fight, flees an unwinnable foe (rotating to a fresh stand and avoiding the\nmob's area), and walks to a healer to resurrect when killed.\n\nThe full guide is **[`BT.md`](BT.md)**.\n\n---\n\n## Renderer — the observation frontend\n\nThe renderer (`src/render/Renderer.*`) is a **software isometric rasterizer** that\nproduces an **ARGB1555** framebuffer (one `u16` per pixel) and hands it to a\n[MiniFB](include/win32/MiniFB.h) window, which does integer upscaling for free.\nIt is a deliberate reimplementation of the original client's draw pipeline, not a\ngeneric engine — the projection, draw order, hue handling, lighting and minimap\nrule are all modelled on `client_2.0.7.exe`.\n\nWhat it draws, in painter's-algorithm order:\n\n- **Land terrain** stretched across each tile's four corner z-values, sampling\n  `art.mul` diamonds and `texmaps.mul` sloped textures.\n- **Static art** from the map blocks, z-sorted against mobiles so same-z mobiles\n  draw above world items correctly.\n- **Dynamic server items** (`0x1A`) — lamp posts, doors (with their open/closed\n  graphic offset), decor — keyed by serial.\n- **Mobiles** (players/NPCs) as `anim.mul` body animations, with:\n  - **Equipment** layered over the body at the same action/frame,\n  - **Hues** from `hues.mul` colour ramps,\n  - **Walk/run cadence** from `animinfo.mul`, with sub-cell sliding so sprites glide\n    between cells in sync with the walk cycle (the local player stays centred and\n    the world scrolls under it),\n  - **Mounts \u0026 chair seating** — a mount body drawn under the rider, or a rider\n    shifted onto a chair seat (ported from `g_SittingChairTable @ 0x55DB68`),\n  - **Combat / death** animation states and war-mode poses.\n- **Animated statics** via `animdata.mul`.\n- **Procedural night lighting:** a per-pixel RGB darkness map seeded by the world\n  light level (`0x4E`/`0x4F`), with smooth radial coronas subtracted for each\n  classified light source (warm for fire/candles, white for lamps; a carried\n  torch/lantern casts a moving pool). A `day [on\u003ccode\u003e|\u003c/code\u003eoff]` toggle forces full daylight.\n\nOverlaid on top of the world frame:\n\n- **Radar minimap** (toggle `M`) — an isometric orientation panel using the same\n  projection as the 3D view, coloured from `radarcol.mul` via the real client's\n  radar rule (topmost surface wins), cached per 8×8 map block, auto-scaled to fit\n  the player and the whole planned route, with route/player/goal markers. Modelled\n  on `CRadarGump_Update` / `CRadarGump_RenderMinimap`.\n- **HUD:** status bars (HP/mana/stamina), a scrolling system log / journal,\n  overhead text, a chat input line, and the UO directional **walk cursor** under\n  the mouse.\n\nThe window also accepts supervision input — see\n[Commands \u0026 window controls](#commands--window-controls).\n\n---\n\n## The target server\n\nThe only supported backend is the reverse-engineered **UO Demo** server:\n**[github.com/draxinar/ouo](https://github.com/draxinar/ouo)**. This client speaks\nexactly that server's flavour of the 2.0.7 protocol (nocrypt, 1997-era move\npacket, no client-side keepalive needed, no step-timing anti-speedhack).\n\nTesting against other servers — official, ServUO, RunUO, etc. — is **not a goal**\nand is not planned. Pointing the client at a different shard is unsupported and\nwill likely break at the handshake or framing layer.\n\n---\n\n## Requirements \u0026 game assets\n\n- **Windows** (the renderer host and build scripts target MSVC; networking uses\n  winsock). The non-Windows compile path exists for the MUL/bot code but the\n  renderer is Win32.\n- **Visual Studio Build Tools** (MSVC, 32-bit) + **Ninja** + **CMake ≥ 3.20**.\n- **Original UO T2A-era MUL data files.** None are distributed with this repo —\n  you must supply them from a legitimate Ultima Online installation. The paths are\n  configured in `src/main.cpp` (default `E:/uo/*.mul`):\n\n  | File(s) | Used for |\n  |---|---|\n  | `tiledata.mul` | Tile flags (walkability, surfaces, doors, light sources) |\n  | `map0.mul`, `staidx0.mul`, `statics0.mul` | Map cells + static art (Britannia, map 0) |\n  | `verdata.mul` *(optional)* | Version word / patch overlay (read-only here) |\n  | `art.mul`, `artidx.mul` | Land + static tile bitmaps |\n  | `texmaps.mul`, `texidx.mul` | Sloped land textures |\n  | `anim.mul`, `anim.idx` | Mobile body animations |\n  | `animdata.mul` | Animated static/dynamic art |\n  | `animinfo.mul` | Mobile walk/run timing |\n  | `hues.mul` | Colour ramps for tinted objects/mobiles |\n  | `radarcol.mul` *(optional)* | Per-tile minimap colours |\n\n  The MUL files are loaded lazily on the first navigation/render that needs them.\n\n---\n\n## Build\n\n```bat\nscripts\\build.bat\n```\n\nThis runs `vcvars32` → `cmake -G Ninja` → `ninja` and builds every target. Output\nlands in `build\\`:\n\n- `build\\uo_client.exe` — the client.\n- `uo_mul.lib` — the MUL loader static library.\n- `uo_mul_dump.exe` — a CLI for dumping tiledata / map cells / walkability.\n\nManual configure/build (with the MSVC environment already active):\n\n```bat\ncmake -S . -B build -G Ninja\ncmake --build build\n```\n\nBuild notes:\n\n- Flags are `/W4 /EHs-c- /GR- /utf-8 /permissive-` — **exceptions and RTTI are\n  off**, so the C4530 warnings from STL headers are expected and harmless.\n- If linking fails with **LNK1168**, a previous `uo_client.exe` is still running\n  and holding the file — close it and rebuild.\n\n---\n\n## Run \u0026 configuration\n\n```bat\nbuild\\uo_client.exe                       :: use built-in defaults + renderer\nbuild\\uo_client.exe --headless            :: pure console client, no window\nbuild\\uo_client.exe \u003chost\u003e \u003cport\u003e \u003cuser\u003e \u003cpass\u003e [gamePort] [gameHost]\n```\n\nConfiguration lives in `src/main.cpp` as a `Client::Config`. The defaults are\n**local placeholders** for the maintainer's LAN (host `172.28.160.1`, login\n`xrip`/`xrip`) and are overridable on the command line — edit them locally or pass\nargs rather than committing your own environment. Key fields:\n\n| Field | Default | Notes |\n|---|---|---|\n| `loginHost` / `loginPort` | `172.28.160.1` / `2593` | override via `argv[1..2]` |\n| `username` / `password` | `xrip` / `xrip` | override via `argv[3..4]` |\n| `version` | `2.0.7` | reported in `0xBD` |\n| `plaintextSeed` | `0xAC1CA001` | = server IP; nocrypt relay token |\n| `sendSeed` | `true` | 4-byte seed prefix on connect |\n| `legacyMovePacket` | `false` | move-packet variant for the demo protocol |\n| `enableKeepalive` | `false` | no client `0x73` keepalive |\n| `acceptDoors` | `true` | A\\* routes through doors, opened at runtime |\n| `enableRenderer` | `true` | open the world window (`--headless` disables) |\n| `*Path` (MUL files) | `E:/uo/*.mul` | see [assets](#requirements--game-assets) |\n| `renderWidth/Height/Scale` | `960×540 ×2` | framebuffer + integer upscale |\n\n---\n\n## Commands \u0026 window controls\n\n**stdin commands** (typed in the console while in-world):\n\n| Command                                    | Effect |\n|--------------------------------------------|---|\n| `goto \u003cx\u003e \u003cy\u003e [z]`                         | One-shot A* path to fixed coordinates |\n| `follow \u003cname\\|0xserial\u003e [distance]`       | Follow a mobile; chase only when farther than `distance` (default 1) |\n| `follow off`                               | Stop following |\n| `mobiles`                                  | Query nearby names (`0x98`), then list `name serialId` |\n| `cast \u003cspellId\u003e`                           | Cast a spell (1-based id) via `0x12`/`0x56` |\n| `skill \u003cskillId\u003e`                          | Use a skill (0-based id) via `0x12`/`0x24` |\n| `use \u003c0xserial\\|type\\|'name'\u003e [pack]`      | Double-click an item by serial, graphic id, or name; searches backpack → worn → nearest world item |\n| `arm\\|disarm [weapon\\|shield\\|both]`       | Move weapon/shield to backpack and back |\n| `pickup \u003ctarget\u003e`                          | Lift nearest matching world item (`0x07`) into backpack |\n| `drop \u003ctarget\u003e \u003cx\u003e \u003cy\u003e [z]\\|\u003c0xcontainer\u003e` | Move backpack item to tile or container |\n| `equip \u003ctarget\u003e [pack]`                    | Wear an item (layer from tiledata `quality`) |\n| `unequip \u003cweapon\\|shield\\|target\u003e [pack]`  | Take a worn item off; drops to world or backpack |\n| `stop`                                     | Abort the current path |\n| `pos`                                      | Print the player's position |\n| `day [on\\|off]`                            | Force full daylight / restore server light levels |\n| `verbose [on\\|off]`                        | Toggle per-packet console chatter |\n| `target ...`                               | Set target cursor |\n| `run \u003cscript.js\u003e`                          | Load + run a JS bot script in a fresh runtime (re-run to reload) |\n| `js stop`                                  | Stop the running JS script |\n| *anything else*                            | Sent as `0x03` ASCII speech |\n\n**Item-target tokens:** `0x…` ≥ `0x40000000` → serial; smaller → graphic id; else → tiledata name. Multi-word names need quotes.\n\n**Render-window controls** (supervision while the bot drives):\n\n| Input | Effect |\n|---|---|\n| **Right-click** a tile | `goto` that cell |\n| **Arrow keys** | Manual single-step walk (throttled) |\n| **M** | Toggle the radar minimap panel |\n| **SPACE** | Send OpenDoor for the faced tile |\n| **TAB** | Toggle war / peace mode |\n| **Type** | Enter the on-screen chat input |\n\n---\n\n## Testing \u0026 regression harnesses\n\nFocused probes live in `tests/`, each with a build/run script in `scripts/`:\n\n| Script / test | What it checks |\n|---|---|\n| `scripts\\build_hufftest.bat` | Huffman compress/decompress round-trip |\n| `scripts\\build_bltest.bat` | `blacklist.mul` (verdata) round-trip |\n| `scripts\\build_pathprobe.bat \u003csx sy sz gx gy [margin]\u003e` | A\\* against the real MULs (path length / node-cap behaviour) |\n| `scripts\\build_viewer.bat [args]` | World-viewer probe (still renders) |\n| `scripts\\build_animprobe.bat` | Animation decode probe |\n\nTwo regression harnesses guard the behaviour-critical paths:\n\n- **`scripts\\path_regression.bat`** — runs two long cross-continent routes (Trinsic\n  bridge ↔ Britain basement, both directions) and regenerates\n  `tests\\path_regression.txt`. Treat `result` / `steps` / `expanded` / `pathCost`\n  as **deterministic** signals (any diff is a real behaviour change to explain);\n  `searchUs` is wall-clock timing, read as a performance trend only. **Run this\n  after any change under `src\\bot\\` or to `World::QueryCell`/walkability**, and only\n  commit the baseline when the change is intended.\n- **`scripts\\render_regression.bat`** — Windows-only; dumps PNG scenes to\n  `build\\regression\\` for visual comparison against the official 2.0.7 client\n  (watch `07_negz_interior.png` for negative-Z interiors). **Run this after renderer\n  changes.**\n\n---\n\n## Project layout\n\n```\nsrc/\n  Client.{h,cpp}          connection state machine, dispatch, movement, bot logic\n  client/ClientRender.cpp per-tick world drawing + HUD glue\n  main.cpp                hardcoded Config + entry point\n  Logger.cpp              JSONL + console packet log\n  net/                    Socket · PacketStream (framing) · Huffman (decompression)\n  builders/Builders.cpp   outbound packet builders\n  navigation/             PathPlanner (threaded A*) · NavigationState\n  bot/                    Pathfinding (A* + grass bias) · Blacklist (overlay + I/O)\n  js/                     QuickJS engine (JsEngine) + Player/World/Mobiles/Vendor bindings\n  mul/                    File · TileData · Map · World · Art · Texmap · Anim ·\n                          AnimData · AnimInfo · Hues · Verdata · RadarColors · dump CLI\n  render/                 Renderer (iso) · Minimap · RadarColors · Text · MiniFBWindow\ninclude/\n  uo/                     shared headers (types, packet ids/lengths, mul, ...)\n  win32/MiniFB.h          windowing\ntests/                    huffman / blacklist / path-probe / viewer / anim probes\nscripts/                  build + regression batch files\n  js/                     JS bot scripts (lumberjack) + lib/ (behaviour runner + skills)\nAGENTS.md · BT.md · ...   developer + design documentation (see below)\n```\n\n---\n\n## Status, limitations \u0026 roadmap\n\n- **Combat in C++ is a hook; the policy lives in JS.** The C++ core only halts the\n  path safely on a HP drop and logs the threat. The actual *engage / flee /\n  resurrect* behaviour is implemented in the JS layer (the threat meter + the\n  lumberjack's DPS-race assessment). **Recall** (spell + reagent/rune handling) is\n  still TODO.\n- **Road bias** uses a minimal grass tile set; expand it with this shard's exact\n  grass/road IDs to sharpen routing.\n- **`blacklist.mul` auto-persist is disabled** (read-only) to avoid poisoning\n  passages — the bot uses transient avoidance only.\n- **`verdata.mul` patching is not applied** (the target server only reads its\n  version word, so base MULs already match).\n- **Map 0 only** (Britannia, 768×512 blocks) is assumed.\n- **Single backend.** Only the [UO Demo server](#the-target-server) is supported.\n\n---\n\n## Developer documentation\n\nSeveral in-repo docs go deeper than this README:\n\n- **[`AGENTS.md`](AGENTS.md)** — repository guidelines: structure, runtime notes,\n  build/test commands, coding style, and the reverse-engineering workflow\n  (including the rule that IDA findings are written back into the IDB).\n  `CLAUDE.md` is a symlink to this file.\n- **[`bot-client.md`](bot-client.md)** — the design \u0026 state notes: protocol flow,\n  movement model, pathfinding internals, obstacle/door/mobile/fatigue handling, the\n  full tunables table, and the known-limitations list.\n- **[`BT.md`](BT.md)** — the bot-scripting guide: the priority behaviour runner,\n  cancellation tokens, the `BehaviorScript` base class and skill mixins, with a full\n  worked example. `scripts/js/globals.d.ts` carries the matching TypeScript types\n  (the source of truth for the scripting surface).\n- **[`JS-BIBLE.md`](JS-BIBLE.md)** — the deeper JS scripting reference.\n\n---\n\n## License \u0026 disclaimer\n\nLicensed under a **custom non-commercial MIT-style license** — © 2026 Ilia\nMaslennikov (xrip). You may use, modify and distribute it **for non-commercial\npurposes only**, must retain the copyright/permission notice (including a link to\nthis repository), and may not sell or monetize it (or derivatives/services) without\nprior written permission. See [`LICENSE`](LICENSE) for the exact terms.\n\n*Ultima Online* is a trademark of its respective owners; this is an independent,\neducational reverse-engineering project and is **not affiliated with or endorsed by**\nthem. No game data files (MULs) are included or distributed — you must provide your\nown from a legitimate installation. The client is intended for use against your own\ncopy of the reverse-engineered [UO Demo server](https://github.com/draxinar/ouo).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fxrip%2Fuo-client","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fxrip%2Fuo-client","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fxrip%2Fuo-client/lists"}