FERRETGame engine over Babylon.js
Ferret

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#

UE5Ferret
Level, World Partitiona level (Levels and packages), level travel
Actor, Actor Component, Blueprint classentity, component, behaviour registered by name
Subsystems, GameMode / GameStateservices; a game's rules as behaviours and services
Character + Character Movement Componentcharacter, with the same parameters
Camera, Spring Armthe camera rig of character, with spring-arm collision
Animation Blueprint, Blend Space, Montage, Notifyanimation graph, blend spaces, montages, notifies
Behavior Tree, AI Controller, NavMeshai
Collision channels and presets, overlapsphysics layers and masks, triggers
Enhanced Inputinput actions and bindings
UMGui, rewritten per widget
Physics Constraint, fractureconstraints and fractured in a level's physics block
Save Gameplatform 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.

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 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:

The model viewer#

pnpm viewer starts tools/viewer: a page that shows a level or an asset, plus an HTTP server that relays commands 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 }).