João Amaro Lagedo
software engineer
EN PT

Elysium: a vampire walks into Unreal

There is a tempting shortcut to rebuilding Vampire: The Masquerade – Bloodlines in Unreal Engine: import a map, add some dramatic lighting, take a screenshot, and declare victory. It would be a very nice screenshot. The first door you try to open would probably disagree.

Elysium is my attempt to bring the 2004 game into Unreal Engine 5.8 while keeping the parts that make it Bloodlines: the authored world, conversations, quests, strange little systems, and the timing between them. Modern rendering is welcome. A guard forgetting why he was guarding a door is less welcome.

Here is what the work actually looks like.

Start with the copy on your machine

The input is an installed copy of Bloodlines, not a folder of ready-made Unreal assets. Its world is spread across Source-era map files, packed archives, models, materials, textures, dialogue, Python scripts, and configuration data. An Unofficial Patch may also replace an original file with a loose one. Pick the wrong version and you can reconstruct a map perfectly while getting the wrong map.

The map resolver is small enough to show. It checks the patch's loose files first, then the base game. The first existing map wins:

for root in LOOSE_ROOTS + [GAME]:
    p = os.path.join(root, "maps", stem + ".bsp")
    if os.path.exists(p):
        return p

That is an abridged excerpt from pipeline/src/elysium_pipeline/formats/install.py. The rest of the installer index applies the same precedence to packed and loose resources throughout the game.

The repository therefore contains code and research, but no game content. A Python pipeline reads your local installation and resolves the files the game would actually use. It decodes them into documented, engine-neutral intermediate data. Geometry and other visual or spatial data can be inspected as glTF/GLB; gameplay data keeps the identities and relationships the runtime will need later.

That middle step matters. If a wall is missing in Unreal, I want to ask whether the decoder found it, whether the intermediate file contains it, and whether the Unreal bake placed it. Each stage can be checked without guessing at the whole game at once.

Diagram: local Bloodlines files go through a Python exporter into inspectable intermediate data, then feed Unreal's visual bake and the C++ gameplay runtime.
The source files take one route into inspectable data, then split into what you see and what happens.

Source, but from another moment

Troika started building Bloodlines while Valve was still developing Half-Life 2. They worked from an earlier branch of Source and added systems for their RPG. The later public Source SDK is useful evidence, but it is not a specification for every file Bloodlines shipped. I spent a lot of time finding where the two stopped matching.

Character animation is a good example. Valve's later SDK describes a chain of per-bone animation records, with flags selecting different compressed position and rotation forms. In Bloodlines, each bone has a fixed 32-byte record with seven references to compressed tracks: three for position and four for a quaternion rotation. The tracks use run-length encoding, or RLE, to hold values across frames. A decoder built for the later layout reads the wrong bytes. I had to recover the older layout from the shipped models, decode its channels, and check actual poses instead of celebrating when a model merely loaded.

Troika also put game-specific events inside animations. A bite clip, for example, can signal when a grapple engages or releases. Import only the skeleton and motion and the character might move correctly while the gameplay event goes missing. A static screenshot will never tell you that.

The Elysium animation notes and event survey document what the game actually ships; Valve's later studio.h shows the layout many Source tools expect.

Give Unreal the stage

An offline Unreal commandlet turns the exported map into local engine assets: geometry, materials, textures, props, decals, lighting, and a loadable level. Unreal can then do what it is good at: render a moody world with modern tools. The look can improve, but the goal is still Bloodlines' grimy, gothic-punk Los Angeles, not a showroom version of it.

Unreal Editor showing Elysium's reconstructed tutorial map, with tall city buildings in the viewport and the mesh Outliner and Details panel visible.
The tutorial map in Unreal Editor. Rebuilt geometry and materials are ordinary scene actors here. Open full size ↗

The Python exporter and Unreal editor do this preparation before play. They are not running behind every frame. At runtime, C++ opens the baked level and uses the exported game data to make that stage act like a game.

Unreal Editor showing a selected point light in Elysium's tutorial alley, with light influence guides and the light's properties in the Details panel.
Lighting the reconstructed alley: the viewport guides and Details panel show some of the work behind the final mood. Open full size ↗

The everyday version of that journey is three commands:

uv run elysium export map sp_tutorial_1
uv run elysium bake map --maps sp_tutorial_1
uv run elysium run play sp_tutorial_1

The screenshot shows the stage. For the behavior, I need something more revealing.

Now make someone live there

Take an NPC in the tutorial. What appears to be one character is assembled from several places: a map entity says where they begin and how they are connected to events; a template supplies stats; native game code decides senses and schedules; scripts add story consequences; animation resolves what the character actually does on screen.

That is why I also use plain test spaces with the game's decisions drawn on screen. The cover points and direction markers in this one are easier to reason about than an NPC disappearing around a beautiful corner.

Elysium development build in a gray test arena, with the player aiming a weapon, gameplay HUD visible, and colored cover and direction markers around a central wall.
An Elysium gameplay test arena. The colored labels expose cover and direction candidates; they are diagnostics, not final UI. Open full size ↗

If that NPC turns the wrong way, rotating the Unreal actor might fix the image and break the scene. The real question is who chose that direction, when, and whether a script or the first AI update should have changed it. The same kind of question comes up with doors, dialogue, combat, and save games. Elysium's C++ gameplay layer reconstructs those rules from the exported data instead of treating every object as a pretty prop.

A door offers a smaller example. Here is the heart of its direct Open input in Source/ElysiumUE/Private/Substrate/ElysiumMover.cpp:

if (IsUseRefused(Activator))
{
    return;
}
if (ToggleState != EToggleState::AtTop)
{
    static const FName OnOpen(TEXT("OnOpen"));
    FireOutput(OnOpen, Activator);
    DoorGoUp(Activator);
}

A locked door refuses that input silently. An admitted input fires OnOpen and calls DoorGoUp, which fires OnOpen again before moving. That double event looks like an easy cleanup, but authored scripts can observe the sequence. Opening a door is suddenly a question about event order, not just hinges.

Timeline: a direct Open input checks whether the door can be used, fires OnOpen, calls DoorGoUp, fires OnOpen again, and eventually fires OnFullyOpen.
Yes, OnOpen appears twice on this path. The sequence is part of the behavior being reconstructed.

I use the original game as an oracle. When behavior is unclear, I inspect the authored files, trace the retail code, document what it reads and writes, and compare a controlled run of the original with Elysium. Sometimes the answer is a missing export. Sometimes it is an event firing one beat too early. Sometimes the original has a quirk that looks like a bug until you discover a quest script depends on it.

The unusual part of the workflow

The code in this repository is written by AI agents under my direction and review. I set the research questions, architecture, and acceptance criteria; the agents implement and investigate. That arrangement makes evidence especially important. A patch that sounds convincing is still just a hypothesis until it survives a build, a test, and a comparison with the game.

The project is still in progress. Individual maps and scripted scenes can be brought up and examined, while broader gameplay reconstruction continues. There is no downloadable copy of Bloodlines here; you need your own installation to generate the game-derived content locally.

What keeps me interested is the detective work between the old game and the new engine. Every time a character does the right thing for the right reason, Unreal feels a little less like a destination and a little more like a new home for Bloodlines.

More on the Elysium project page, or follow the source and research notes on GitHub.