# Porting from Unreal Engine 5

# Porting from Unreal Engine 5

Ferret's first task is porting games made in Unreal Engine 5. Its features follow what UE5 games use, and `tools/ue-export` carries a level across.

## The importer app (Caroline's tools)

When Unreal Engine is on the person's own Windows computer, nobody runs the scripts by hand: the Ferret UE importer/exporter, a tray app there, does the work, and Caroline in the editor drives it.

- `ue_places` lists the account's apps (online or not) with their UE versions, projects and maps.
- `ue_import {project, map, level?}` brings a map in as a new level: geometry with baked materials, environment, collision, physics, landscape. A level of that name must not exist yet.
- `ue_export {level, project, map?}` writes a level into a UE project as a new map under `/Game/Ferret/<level>/` (models and textures by Interchange, the actors, the lights, a `PlayerStart`); nothing else in the project changes.
- `ue_status` follows a job (a finished import brings its level into the project), `ue_cancel` stops it. A job that runs longer than about 3 minutes answers with an id; ask `ue_status` later.
- The result's report names what was not mapped; the Blueprints and the parity check remain the work of the steps in the porting skill.

If no app is online, the person installs it from https://ferret.partners.solutions/ferret/download/ue-bridge/ferret-ue-bridge-latest.exe (the editor's menu **UE > Download the UE importer/exporter**), runs it and signs in with their SW account in its panel. It is Windows only and needs Unreal Engine installed there; there is no cloud variant.

## UE concepts and their Ferret equivalents

| UE5 | Ferret |
|---|---|
| Level, World Partition | a level ([Levels and packages](levels.md)), level travel |
| Actor, Actor Component, Blueprint class | entity, component, behaviour registered by name |
| Subsystems, GameMode / GameState | services; a game's rules as behaviours and services |
| Character + Character Movement Component | `character`, with the same parameters |
| Camera, Spring Arm | the camera rig of `character`, with spring-arm collision |
| Animation Blueprint, Blend Space, Montage, Notify | `animation` graph, blend spaces, montages, notifies |
| Behavior Tree, AI Controller, NavMesh | `ai` |
| Collision channels and presets, overlaps | `physics` layers and masks, triggers |
| Enhanced Input | `input` actions and bindings |
| UMG | `ui`, rewritten per widget |
| Physics Constraint, fracture | `constraints` and `fractured` in a level's `physics` block |
| Save Game | `platform` state |

What has no equivalent (Lumen, Nanite, Chaos destruction, MetaHuman rigs) is replaced per game: baked lighting, LODs, prebaked destruction, simplified rigs. The porting report lists each case.

## The pipeline

1. **Geometry and materials.** `ue/gltf.py`, run inside the Unreal editor, exports the level with UE's glTF exporter, materials baked to PBR. It needs rendering on (`-AllowCommandletRendering`) and compiles the level's materials first, otherwise the baked textures come out empty.
2. **Environment, collision, NavMesh settings, landscape, physics.** The other scripts in `tools/ue-export/ue/` write the level's sun and fog, its simple collision shapes, NavMesh build settings, landscape layers and physical materials next to the glTF.
3. **Blueprints.** `blueprint_text.py` and `blueprints.py` dump each mapped Blueprint class as a spec, stored next to the Ferret behaviour that replaces it. A mapping from the game (`Blueprint class → behaviour + params`) turns the level's actors into manifest entities. Unknown classes are listed in a report, not dropped.
4. **Post-processing.** `postprocess.mjs` (glTF-Transform) prunes, instances repeated meshes (`EXT_mesh_gpu_instancing`), simplifies foliage, scales and re-encodes textures per tier, and optionally compresses to KTX2 and meshopt.
5. **Characters and mobs.** `inventory_characters.py` lists every skeletal mesh, skeleton, animation Blueprint, sequence, blend space and montage of a project (size, bones, morphs, which skeleton each clip is on). `characters.py` exports named skeletal meshes and animation sequences to GLB with the same glTF settings (`FERRET_ASSETS`, `FERRET_OUT`, `FERRET_ANIMS=1` to carry a skeleton's clips with the mesh), one file per asset and an `export.json` report. What is game-specific stays in the game: which meshes make a character, preset and customizer data, material values.

### Landscapes (layers, far backdrop, virtual texture)

A level may have several Landscape actors (a near ground and a far mountain plane). `ue/landscape.py` writes `landscape-<label>.json` for each (heights from the collision, `LAND_STEP_<label>` per-landscape step). `ue/landscape_layers.py` writes the paint layers, one greyscale weightmap per layer over the landscape's bounds, the landscape material's texture parameters and the RuntimeVirtualTextureVolume settings; `landscape-weights.mjs` packs four layers per RGBA image. `postprocess.mjs --landscape spec.json` with `{ "landscapes": [{ "file": "landscape-Landscape3.json", "name": "Landscape3", "stride": 8, "far": true }, { "file": ..., "name": "Landscape", "layers": [...], "weights": [...], "virtualTexture": { "meshes": "^SM_rock" } }] }` builds one mesh per landscape (the far one decimated, kept out of the collision) and writes them into `environment.landscapes`, which `@ferret/levels` applies at load (`landscape.ts`): a material plugin blends the layers per pixel by the weightmaps (optionally height-blended), the far plane is marked never walked or picked, and the blended colour is baked once into a texture the named props sample near their feet (the runtime virtual texture's job in Unreal). The layer textures, their tiling and the virtual-texture meshes come from the landscape material's graph (`materials.py` writes it as text), so the spec is written by reading that graph.

```bash
node tools/ue-export/postprocess.mjs level.glb out/ --tier all --ktx2 --meshopt \
  --environment environment.json --collision collision_shapes.json --nav nav.json
```

The output is a level folder (and per tier) that the [level service](levels.md) loads, or a `.ferret` package.

## Porting a game

1. **Inventory**: list the levels, classes, plugins and UE features in use; the porting report says what maps as is, what needs a rewrite and what has no equivalent.
2. **Mapping** of each UE class to a behaviour.
3. **Rewrite** of behaviours, services and screens from the specs, one class at a time, with tests in the fixed-step loop.
4. **Export** of each level; the model viewer and agents check the result.
5. **Parity check**: the ported level is compared with the original from the same cameras, along the same walked paths, with the same triggers firing.
6. **Re-export** when the original changes; the game's edits on top of the export stay, because the manifest refers to nodes by name.

## Lessons from real ports

Most of the time in a port goes to things that run without an error and come out wrong. Three pages collect what was found:

- [Running the Unreal side of a port](ue-export-runs.md): run settings that give complete output, and the traps of the export runs.
- Making a ported level look right (skill `ferret-unreal-level-look`): black or dark surfaces, smeared materials, wrong colours, flat light, with causes and fixes; find the cause before changing anything.
- Parity with the original (skill `ferret-unreal-level-look`): the same views shot in Unreal and in Ferret, the axis mapping, and reading the differences.
- Porting characters and animation (skill `ferret-unreal-characters`): clips, parts, hair, morph targets, size, Animation Blueprints.

## The model viewer

`pnpm viewer` starts `tools/viewer`: a page that shows a level or an asset, plus an HTTP server that relays commands (skill `ferret`) from clients (agents, tests, an MCP server) to the open page and back, serves screenshots of the active camera, and saves edited manifests. `pnpm viewer:headless` opens the page without a screen, so a CI run or an agent needs none. The viewer grows into the level editor.

**Characters.** `?characters=<index.json>` (or `--characters` on the viewer server) turns the page into a character viewer for what a game exported: a list of the characters, the clips of the clips file (or the ones in each character's own file) as buttons, "play all", and a report on each character: a file that does not load, a mesh without a skin or a material, a clip with no track on this body or that moves nothing; clip bones the body does not have are noted, not counted as problems. The index is a small JSON the game's build writes next to the files, urls relative to it: `{ "clips": "cc_clips.glb", "characters": [{ "name": "CC_Female_Preset_01", "url": "low/CC_Female_Preset_01.glb" }] }`. A character made of modular parts lists their files in `"parts"` (one file per part, one skeleton): the viewer shows them together and gives each the clips; `url` is not loaded then. A character may carry its own `"clips"` file, which wins over the index's (families with a skeleton of their own: NPCs, animals). Commands for agents: `characters.list`, `characters.show {name}` (answers with the report), `characters.play {clip}`, `characters.playall {seconds?}`, `characters.stop`, `characters.check {name?}` (every character when no name is given: `{ reports, problems }`).
