The MJOLNIR level pipeline is this game's answer to the old Halo Editing Kit: author a level in Blender, place vehicles and weapons like Sapien's scenario editor, bake it like Tool, and play it in the real game. This guide walks the whole loop. The file format reference is level_format.md.
Converting a classic CE map? That no longer needs a canvas's geometry:
tools/level/convert_ce_map.shbuilds the map its own structure BSP and a standalone multiplayer scenario in one command. See ce_map_conversion.md.
What a custom level is (and isn't) — read this first
A v1 custom level is a map variant: it runs on top of one of the 13 shipped campaign maps (the canvas), whose world and collision stay loaded. That's not a stopgap aesthetic choice — it follows from how the game works:
- The Blam simulation owns all gameplay collision, and it collides only with its own world: the canvas map's BSP plus Blam objects. Unreal geometry — cooked or runtime-spawned — is walk-through, drive-through and shoot-through, always (verified 2026-08-19, multiplayer_investigation_notes.md).
- Solid structures are therefore built the way the original Forge community built them: from placed Blam objects (scenery and crates), which carry their own collision. The bake turns your structures into scenario-tag placements.
- Unreal meshes still have a place — as decor: any of the game's ~12k static meshes, tinted engine shapes, skyline dressing. They render; they do not exist to the simulation.
Everything solid comes from the scenario override; everything cosmetic comes
from a runtime mod reading your level file. One .level.json describes both.
Setup (once)
| What | How |
|---|---|
mjolnir CLI | cargo build --release -p blam-cli (or a release build) |
| Blender 4.0+ addon | scripts\install-blender-tools.ps1 (installs and enables it, plus blender-mcp), or install tools/blender/mjolnir_level by hand (its README) |
MJOLNIRLevelLoader mod | the launcher installs it, with its native DLL, when it installs multiplayer or a map. scripts\install-bridge.ps1 -Mods MJOLNIRLevelLoader copies the repository's Lua instead and replaces the whole folder, removing the installed levels/, registry/, variants/ and native DLL (build the DLL first with native\map_registry\build.ps1 to keep it) |
| Mesh catalog (for the decor library) | mjolnir mesh list --paks <Paks> --out mesh-catalog.json (~3 min) |
<Paks> is <game>\Meteorite\Content\Paks; set HCE_PAKS once and drop the
--paks flags.
1 — Author in Blender
Open Blender, sidebar (N) → MJOLNIR:
- Set the level slug, the canvas scenario (B40 has the fullest vehicle palette: warthog, ghost, scorpion, banshee, wraith, shade), and the origin — the UE-centimeter point on the canvas map where Blender's world origin lands. Build around your origin; relocating the level later is a one-field edit.
- Lay the level out in meters, Z-up, as usual. Give every gameplay object a
Role in the object panel:
- Player start ×2+ (co-op needs two)
- Vehicle / Weapon / Equipment with a type (
warthog,sniper_rifle,overshield, … — the full list isdefs/level/palette-map.json, which also says what each canvas already carries) - Structure with a Blam tag path and group (
scenery/crates) — your ramps, walls and cover; these are the solid ones - Decor mesh for visuals (use Place shipped mesh… to browse the catalog as real-size proxy boxes)
- Export .level.json.
Keep the playable area on the canvas map's real ground: the simulation's floor is the BSP, wherever your meshes are. Yaw-only rotations are the safe kind; compound rotations compose differently between the engines.
2 — Bake and install
mjolnir level validate my_level.level.json
mjolnir level bake my_level.level.json --install-test
To ship the level as a mission of its own instead of an override of the
canvas mission, add --standalone <CODE> (three characters): the bake writes
the scenario as a new tag package under /Solo/<CODE>/, registers the
codename (a cooked DT_Scenarios row carrying the level's title and
description, plus the campaign's ScenarioList handle — the simulation
builds its map registry from those tables at boot, see
new_scenario_loading.md), and installs the decor
file as <CODE>.level.json. --bsp INDEX=PAYLOAD clones structure BSP
INDEX of the canvas scenario under the codename too, with PAYLOAD as its
body, so custom collision no longer overrides the shipped mission's BSP:
mjolnir level bake examples/levels/bloodgulch.level.json --standalone BGL --bsp 8=<staging>/bloodgulch/collision/bsp_01_1_start.bin --world <bare.umap> --install-test
--world <bare.umap> gives the map an Unreal world of its own (the MapKit's
empty world, kept as defs/level/empty_world.umap, renamed under the
codename) instead of running on the canvas
mission's. That needs the MJOLNIRLevelLoader mod with its native half,
native/mjolnir_map_registry.dll (built by native/map_registry/build.ps1,
shipped by the mods release): the engine resolves a mission's world by its
short name through the AssetRegistry, which only knows shipped packages, and
the DLL answers for worlds found in the installed containers
(new_scenario_loading.md, "The world gate").
Without it the launch bounces back to the menu; without --world the map
runs on the canvas world and needs nothing native.
Every installed map shares one registration container,
pakchunk996-MJOLNIRREG_P.*. The bake builds it from this map plus every map
recorded in the loader's registry/ directory, so two standalone maps
register side by side. To undo one map, delete its three
pakchunk997-MJOLNIRMAP-<CODE>_P.* files and the loader's
registry/<CODE>.json, levels/<CODE>.level.json and maps.json (the
bake's menu list, which still names the map), then run
mjolnir level register: it rebuilds the shared container and the
multiplayer menu's map list (ue4ss/MJOLNIRMaps/maps.json) from the maps
that remain, and removes the container when none do. Deleting the shared container unregisters every map.
bake clones shipped placements and re-points them (position, palette entry,
a fresh unique id — never a novel string id, which the native parser rejects),
grows palettes when a structure's tag isn't in them yet, rewrites the
insertion-point-0 player starts, and writes the result through the same
byte-exact container verification mjolnir pack uses. --install-test puts
the three pakchunk998-MJOLNIRLEVEL-* files in Paks and copies the level
file to the loader's levels/ directory.
To iterate on decor only, edit the level file in place and run
mjolnir_level_reload in the in-game console — no restart. Scenario-side
changes (starts, vehicles, weapons, structures) need a re-bake and a game
restart.
To uninstall: delete the three pakchunk998-MJOLNIRLEVEL-* files and the
loader's levels/<CANVAS>.level.json, or the shipped mission keeps the
level's decor.
3 — Play
Start the game and launch the canvas mission through the game's own menu
(Campaign → mission select, or the debug map menu via mjolnir_debug_ui).
Do not use mjolnir_mission for a cold start — on current builds it loads
the world but never starts the simulation (see the 2026-08-19 investigation
notes).
In-game console (~):
mjolnir_level_status— what the loader found and spawnedmjolnir_level_reload— re-read the level file, respawn decormjolnir_level_clear— remove spawned decor
Two-player: the second player joins through the normal invite co-op flow and spawns at your second player start.
Limits (v1, honest)
- The canvas mission still runs underneath unless cleared — its AI,
objectives and music. The Blender export already clears the mission's
vehicles, weapons, equipment and placed bipeds (
blam.clear). Add"squads": trueto remove its AI, and"scripts": trueto replace its script with one startup script that fades in and hands the player control;blocksempties any other root block by name.examples/levels/blank_b40.level.jsonstrips B40 to bare geometry this way (level_format.md). - Vehicle, weapon and equipment types the canvas palette lacks get a palette
entry of their own, cloned and re-pointed, the same way structures do; the
converted CE maps get the shotgun and fuel rod, which B40 lacks, this way.
scenario_palettesindefs/level/palette-map.jsonlists what each canvas carries already. - Rotation conventions beyond yaw are provisional until verified in-game.
- This addon's map variants keep the canvas map's BSP. A level with its own
BSP and lighting goes through the CE route instead: build it as a classic
CE map (Blender to JMS, then the classic HEK), and
tools/level/convert_ce_map.shgenerates its Blam collision (mjolnir level collision --own-bsp) and, when the Unreal cook is on, re-solves its lightmaps (mjolnir level lightmaps).levels/test/tst/is a worked example (pipeline_test_map.md).
How it hangs together
Blender (+ mjolnir_level addon)
└─ my_level.level.json
├─ mjolnir level bake ──► scnr override container (_P) ──► Paks
│ solid: starts, vehicles, weapons, equipment, structures
└─ copied verbatim ─────► ue4ss/Mods/MJOLNIRLevelLoader/levels/
cosmetic: decor meshes, spawned on world load