Research notes

Forge pack utility metadata

Raw investigation log, rendered from the repository. Curated summaries live under Documentation.

Pack utilities are bounded catalog data. They do not install scripts from a community archive. The Hub scanner, CLI packer and launcher validate the same fields; the launcher preserves them in MJOLNIRForgePacks/installed.json. Old packs can omit them or use null.

Gravity lifts

A block can carry this optional specification, in its authored local axes and metres (right-handed, Z up):

"lift": {
  "offset_m": [0, 0, 0.45],
  "size_m": [4, 4, 3.5],
  "direction": [0, 0, 1],
  "acceleration_m_s2": 20,
  "max_speed_m_s": 4.5,
  "negates_gravity": true
}

offset_m is the trigger's bottom centre; size_m is its full box size. The trigger follows the piece's live translation and full rotation, independently of its visual mesh normalization. The example overlaps a 0.5 m deck by 5 cm to avoid a floating-point gap. Foundry Wide uses this four-metre-rise preset: its nominal column ends at 3.95 m, with an additional ballistic rise after exiting. Verify the trajectory and landing in your own layout; the speed is not a guaranteed landing height. Horizontal steering is preserved.

Offsets are limited to ±100 m per axis; sizes to 0.1–100 m. Direction must be a unit vector (squared-length tolerance 0.001). Acceleration is 0.1–100 m/s², speed 0.1–30 m/s, and negates_gravity must be a boolean. Unknown fields are rejected. Runtime discovery uses at most 16 class definitions, selected by sorted class path, and 64 active lift actors; additional definitions are ignored.

MJOLNIRLevelLoader/Scripts/pack_lifts.lua converts these boxes to the existing Halo 2 phantom format. lifts.lua uses the same tested acceleration, gravity compensation and object_set_velocity script transport as Lockout's lifts. Each peer pushes only its own locally controlled Spartan bipeds, in Forge and Slayer. Saved-layout actors and later duplicates are discovered from their current Blam bindings; hidden or pooled actors without a binding do nothing.

Discovery uses Unreal's native actor-class lists and nonloading soft-class resolution, at 500 ms while idle and 50 ms near a lift. It never performs a periodic UE4SS global object scan or retains a Blueprint class/actor wrapper between calls. Travel clears the carried velocity and pending world state. The existing LevelLoader script writer remains shared with static Halo 2 lifts and kill floors.

Automatic doors

Panels may declare:

"door": {
  "travel_m": 4,
  "speed_m_s": 4,
  "trigger_size_m": [8, 16, 6],
  "hold_open_s": 2
}

Travel is along local +Z. Travel must be positive and at most 16 m, speed positive and at most 8 m/s, trigger dimensions positive and at most 32 m, and hold time 0–10 seconds. Door panel dimensions_m may not exceed 64 m on any axis. Unknown fields are rejected. The infantry preset above travels 4 m; the vehicle preset travels 6 m with a [12,20,8] trigger, the same 4 m/s speed and two-second hold.

Native behavior

Use MJOLNIRForge 0.4.0, MJOLNIRLobby 0.19.2 and MJOLNIRLevelLoader 0.9.8 or newer together. Lobby checks the Forge native utility API marker and waits for a compatible native Forge module before animating. It refuses door automation with an older loaded Forge DLL, whose save path would otherwise serialize the animated position.

native/lobby/forge_doors_runtime.h runs on the authoritative simulation thread in Forge and ordinary multiplayer modes, including Slayer. It operates on the native crate and its collision body through the engine's transform setter, rather than moving only its Unreal visual. Players approaching the rotated local trigger request opening along the panel's local +Z. After they leave, the panel waits for hold_open_s and closes. Fixed and Phased enable automation; Normal disables it and leaves ordinary physics in control. Fresh scenario construction and Revert seed installed door panels to Fixed, because scenario placements do not encode this physics choice. This happens before the objects are created. Loading a saved layout replaces those defaults with its saved properties, including an explicit Normal choice; other pieces and existing Fixed/Phased choices are unchanged.

The runtime checks a conservative sphere against the panel's swept box before closing. Bipeds, vehicles and movable Normal crates can keep a door open or reopen it. Opening also stops beneath a dynamic obstacle. Entering Monitor mode, or requesting an edit, attempts to close the panel only if its entire closing path is clear. A parked vehicle can therefore leave the panel raised while editing. Grabbing, duplicating, rotating or changing the physics of that raised panel is deferred with a message to clear the doorway; unrelated objects remain editable. These checks do not test the static world, Fixed/Phased construction pieces, or unparented crates whose native physics model contains only fixed bodies. The last classification is cached per world and salted tag; keyframed, mixed and unknown bodies remain obstacles. Authors must leave space above and beside the panel for its full travel.

Motion advances once per changed simulation tick, using the native tick rate; paused redraws do not consume hold time. Catch-up frames advance at most one tick. There are at most 128 active automated panels and 256 installed door definitions. The adapter scans native object arrays once per tick, with no Unreal global-object polling. It accepts only supported CU4 builds and rejects parented panels, invalid transforms, stale object handles and unmatched pack identities. Unsupported builds or invalid inventory disable automation.

Editing, saves and multiplayer limits

Forge's native update reapplies each map-variant pose every tick. The adapter therefore publishes the animated position in the live map variant and keeps the authored closed pose in a private cache, keyed by world instance, full object handle and entry identity. Deletion, travel and replaced objects discard stale cache entries; an external authored pose change establishes a new baseline.

F5 projects verified closed poses into Forge's temporary serialization copy. Saving does not close the live door or modify the active animation. If a panel has no verified closed baseline, saving is deferred rather than recording its raised pose. This includes enabled door panels on fireteam clients. Ordinary layouts without those panels retain the existing client-save behavior.

A client that becomes host cannot reconstruct the closed origin of a door it joined while open. Automation and saves requiring that missing baseline remain disabled until the game is rehosted. Closed-origin state is not separately replicated. The engine's object-transform updates are used in Slayer; map-variant dirty updates apply in Forge. Client visual/collision replication, host migration and driving a vehicle beneath an operating gate still require in-game verification. The validation record reports the narrower local save/reload, visible opening/closing and nearby-vehicle obstruction results. The implementation and portable tests are not live evidence.

Installed companion

The launcher emits a companion MJOLNIRForgePacks/doors.txt for the native reader. Its header is forge-doors 1; each row has ten tab-separated fields: tag, travel, speed, trigger X/Y/Z, hold time, panel width/depth/height. All measurements are metres or seconds, using decimal dots. There are at most 256 definitions across enabled packs. The native reader must match each tag against current installed.txt bindings. Panel dimensions come from the validated catalog, not a guessed donor bound. The trigger is centred on the panel in X/Y, with its vertical interval from −0.5 m to trigger Z above the authored bottom origin.

The launcher prepares all inventory files before replacing each atomically, publishing the identity bindings last. Disabling/uninstalling packs also rewrites the companion; no door entries leaves a header-only file.

Surface profiles

surfaces is an optional list of up to 16 strict {slot, profile} objects. Slots are unique integers 0–15. Profiles are the fixed names glass_cyan and teleporter_green; arbitrary material paths or code are not accepted. The material implementation supplies the approved textures, animation and shader parameters. The green visual does not replace native teleporter sender/receiver/two-way markers and their channel settings.

The CLI packer and Hub scanner require these cooked packages inside the archive's installed content/ containers, under /Game/MJOLNIR/Forge/<pack-id>:

ProfileRequired package paths below the pack root
glass_cyanMaterials/M_ForgeGlassCyan
teleporter_greenMaterials/M_ForgePortalGreen, Textures/T_PortalDust, Textures/T_PortalMask, Materials/T_CE_BlackCube

The check uses Unreal package IDs and requires export-bundle chunks; bulk-only chunks, another pack's copies and containers outside content/ do not satisfy it. Packs without surface profiles do not need these assets. The Hub's bounded package-ID hash and the Rust implementation share 15 test vectors spanning minimum, ordinary and maximum pack slug lengths.

This is a presence check, not proof of valid material exports, complete shader code, matching mesh slots or gameplay collision. Door/lift metadata validators check their bounded data shapes; they do not decode tag payloads to prove a compatible native crate and Blueprint binding. The authored cook checks and live runtime checks remain necessary, as does testing the installed pack.

Checks

Portable tests cover transformed trigger planes, source-to-Unreal coordinates, the standing-deck overlap, player ownership, actor pooling, class lookup cost boundaries, world travel and use of the shared script writer. Existing Halo 2 math tests remain unchanged. Hub/CLI/launcher tests cover schema limits, unknown keys, normalized inventory round trips, companion fields and stale-row removal. Live gameplay coverage is recorded separately in the validation record.

native/lobby/test-forge-doors.ps1 runs the portable door parser and movement tests with MSVC warnings treated as errors. They cover field limits, rejected row isolation, translated/rotated triggers, sphere-versus-swept-panel clearance, opening/closing, hold time and paused or invalid time steps. The same C test also builds with a C11 compiler on other platforms. CI runs it alongside the three lift Lua suites and the launcher Forge inventory tests.