Case study

Silkroad Online in the browser.

A 2005 MMORPG, ported to WebGL with AI in 3 days. Live footage of the build, running on a GPU.

Play itBack home

from the first commit to this build
3 days
lines of TypeScript
46,000+
unit tests, all passing
1,100+

Jangan, from its terrain files

The world is a file.

52 lines, one per row of the real heightmap of the town and the eight regions around it. Nothing was modelled by hand: the port reads the same bytes the game did.

01

Read the closed formats, then prove it.

The game ships its data in an encrypted archive, with textures, terrain, meshes and animations in formats nobody documents in one place. Each one was decoded with a hypothesis, tested against the real files, and kept only when a test could fail. A guess that renders is not a decoder.

Media.pk2, bytes 0 to 127
000000004a6f794d61782046696c65204d616e61JoyMax File Mana
00000010676572210a0000000000000000000200ger!............
00000020000101d8da3000000000000000000000.....0..........
0000003000000000000000000000000000000000................
0000004000000000000000000000000000000000................
0000005000000000000000000000000000000000................
0000006000000000000000000000000000000000................
0000007000000000000000000000000000000000................

The first twenty bytes spell the name of the archive. Everything after is encrypted with Blowfish.

02

Treat the game as data.

The server database was restored in a container and exported to JSON: items, skills, monsters, spawns, shops and drops. The port reads the same tables the game did, so a skill costs what the game says it costs.

skills/0.json, one real row
{
  "activity": 2,
  "cast": 411,
  "duration": 1022,
  "reuse": 3000,
  "target": 24,
  "needsTarget": true,
  "mpCost": 19,
  "mastery": [
    [257, 5]
  ],
  "sp": 2,
  "weapon": [2, 3],
  "icon": "skill/china/sword_smash_a.ddj",
  "id": 3,
  "group": 174,
  "code": "SKILL_CH_SWORD_SMASH_A_01",
  "name": "SN_SKILL_CH_SWORD_SMASH_A",
  "lvl": 1
}

One of 31,000+ skills, read straight from the game database.

03

The best port is the one you do not write.

The original interface turned out to be data too: a layout file per window. A converter and a generic renderer draw every window exactly as the client does, instead of redrawing them by hand. The sounds come from the game's own event table.

ifunderbar.json, a few controls
[
  {
    "n": "GDR_STATIC_EXP",
    "c": "Static",
    "id": 18,
    "r": [18, 31, 198, 12],
    "col": [255, 193, 110, 203],
    "fc": [255, 241, 255, 220],
    "ha": 1,
    "va": 0
  },
  {
    "n": "GDR_GAUGE_SP",
    "c": "Gauge",
    "id": 16,
    "r": [17, 11, 144, 8],
    "ddj": "interface/underbar/ub_sp_bar.ddj",
    "col": [255, 85, 102, 50],
    "ha": 1,
    "va": 0
  },
  {
    "n": "GDR_BTN_MENU",
    "c": "Button",
    "id": 4,
    "r": [688, -17, 60, 56],
    "ddj": "interface/underbar/ub_menu_button.ddj",
    "col": [255, 228, 199, 253],
    "va": 0
  }
]
The bottom bar of the game as the port draws it from that layout: the quick slots, the experience cells and the menu.
3,600+ controls like these draw every window.
The skills window of the original client, drawn by the port, with a mastery raised and five skills learned.

04

Write the rules by hand where the data stops.

Skill points, masteries and combat live in the game's server, not in its files. They are written from evidence, and the code says, line by line, what is verified and what is assumed, so a number is never quietly invented.

progression/rules.ts

The rules of the character build: attribute points, mastery levels and learning skills with skill points (SP). Pure: no entities, no DOM.

WHAT IS VERIFIED AND WHAT IS NOT * A skill row asks for a mastery level (mastery: [id, level]), for STR / INT, for earlier skills (reqSkill: [[group, level]], VERIFIED: the numbers are skill GROUPS, e.g. Smash B asks for group 174 level 9) and costs sp skill points. VERIFIED on the table: sp equals the expM column of _RefLevel at the mastery level the skill asks for for 1855 of 2110 priced skills (mastery 5 -> 2 SP, 27 -> 117, 49 -> 449); the other 255 are all high level skills (mastery 70+) that cost 10 % to 60 % MORE than that, so expM is the SP price scale with a premium on top. * Raising a mastery by one level costs expM of the NEW level. ASSUMPTION (same scale; the server owns the real rule). * A mastery cannot pass the character level and all masteries together cannot pass the character level (the client has the texts "Cannot learn it any longer due to mastery limit" / "...total mastery limit"). The exact total is the server's; this is the rule used. * 3 attribute points per level, spent on STR or INT, from a base of 20 / 20: the values of the vSRO character creation as the community remembers them (not verified here). * Skills are learned one level at a time (level 1 of a group first, then 2, ...).

05

Verify like a user, every step.

Each phase is deployed, opened in a real browser, and its screenshots are read one by one. Tests run against the real game, not mocks. Work is split between agents with strict file ownership, and every commit is checked in a clean copy of the tree before it ships.

terminal
$ npm test
Test Files  83 passed (83)
Tests  1153 passed (1153)

$ npm run test:e2e
195 browser tests in 18 specs, against the real game

The numbers.

  • 3 daysfrom the first commit to this build
  • 46,000+lines of TypeScript
  • 1,100+unit tests, all passing
  • 190+browser tests against the real game
  • 31,000+skills read from the game database
  • 13,000+monsters, NPCs and models
  • 3,600+interface controls drawn from the original layouts
  • 5,600+rules in the original sound table

Counted on 2026-10-10. Counts are rounded down.

What it is not, yet.

It is single player. Combat uses my own damage model, not the original formula, which lives in the server. Skills that apply status effects are shown but not playable, and visual effects are still to come. Phones play with the simpler touch interface. The way to exact rules is to run the real server behind the same client, and that is the next step.

Credits and disclaimers

  • Not affiliated.

    A fan project with no relation to Joymax or any publisher of Silkroad Online. Silkroad Online and its names, art, sounds and music belong to their owners.

  • The game files are not mine.

    The port contains no game assets of its own. The demo reads them from the original client's files on a private server of mine and shows them in your browser. They are not offered for download and not for any other use.

  • Not a game server.

    It is single player. No accounts, no online play, no connection to any Silkroad server, nothing sold. It is an engineering exercise, not a replacement for the game.

  • Experimental software.

    Most of the code was written with AI and tested, but it is a prototype: it can have bugs, and the rules are my own model, not the original game's. Saves stay in your browser and can be lost.

  • Takedown.

    If you own rights to any of this and want it changed or removed, write to omar@hernandezbastos.es and the demo comes down.

Go and play.

About 40 MB the first time. Best on a desktop with a mouse.

Play itBack home