RGM Scene File Format
Scene/map container with sectioned records for placed objects, script metadata, and auxiliary world data. The RA*-prefixed sections (RASC, RAHD, RAAT, RAHK, etc.) form the per-map SOUP scripting layer — see SOUP Scripting for a consolidated map of all script data sources and runtime boundaries.
Section Framing
Each section starts with an 8-byte header. Some sections then include a 4-byte little-endian record_count word at the beginning of section data.
| Offset | Size | Type | Endian | Name | Description |
|---|---|---|---|---|---|
| 0x00 | 4 | [u8; 4] | — | section_name | ASCII section tag (for example RAHD, MPOB, MPSO, END ) |
| 0x04 | 4 | u32 | BE | data_length | Payload size in bytes (0 for END ). Big-endian in section-framed formats (RGM, PVO, ROB, TEXBSI). |
For count-prefixed sections (MPOB, MPSO, MPRP, and several others), section payload begins with:
Little-endian.
| Relative Offset | Size | Type | Name | Description |
|---|---|---|---|---|
| +0x00 | 4 | u32 | record_count | Number of fixed-size records in this section |
Sections are parsed sequentially until END .
MPOB (Object Instances)
MPOB starts with a little-endian object count, followed by 66-byte records.
All fields little-endian.
| Offset | Size | Type | Name | Description |
|---|---|---|---|---|
| 0x00 | 4 | u32 | id | Object id |
| 0x04 | 1 | u8 | object_type | Object kind discriminator |
| 0x05 | 1 | u8 | is_active | Activation flag |
| 0x06 | 9 | [u8; 9] | script_name | Script/object name |
| 0x0F | 9 | [u8; 9] | model_name | Model reference |
| 0x18 | 1 | u8 | is_static | For a type-1 placed-model fallback, 1 selects a static model; other values give the animation frame-table length. See placed-object frame sources. |
| 0x19 | 2 | i16 | reserved | Not read at runtime. |
| 0x1B | 3 | i24 | pos_x | Position X (fixed scale) |
| 0x1E | 1 | u8 | pad_x | Alignment byte |
| 0x1F | 3 | i24 | pos_y | Position Y (fixed scale) |
| 0x22 | 1 | u8 | pad_y | Alignment byte |
| 0x23 | 3 | u24 | pos_z | Position Z (fixed scale) |
| 0x26 | 4 | u32 | angle_x | Bethesda 2048-unit Euler angle |
| 0x2A | 4 | u32 | angle_y | Bethesda 2048-unit Euler angle |
| 0x2E | 4 | u32 | angle_z | Bethesda 2048-unit Euler angle |
| 0x32 | 2 | i16 | texture_data | Packed texture id/image id |
| 0x34 | 2 | i16 | intensity | Light/intensity-like field |
| 0x36 | 2 | i16 | radius | Radius-like field |
| 0x38 | 2 | i16 | model_id | Model index/id-like field |
| 0x3A | 2 | i16 | world_id | World index/id-like field |
| 0x3C | 2 | i16 | red | Color channel |
| 0x3E | 2 | i16 | green | Color channel |
| 0x40 | 2 | i16 | blue | Color channel |
Position decode used by current exporter:
- scale constant:
1 / 5120 x = -(pos_x * 256) * scaley = -(pos_y * 256) * scalez = -(0x00FF_FFFF - (pos_z * 256)) * scale
Bethesda 2048-unit Euler angles:
2048 discrete units represent a full 360° rotation — a power-of-two binary angle encoding. The raw u32 is reduced modulo 2048 (equivalently masked with & 0x7FF), giving a value in the range [0, 2047]. Each unit equals 180/1024 ≈ 0.176°.
degrees = (value % 2048) * (180.0 / 1024.0)
| Units | Degrees |
|---|---|
| 0 | 0° |
| 512 | 90° |
| 1024 | 180° |
| 1536 | 270° |
| 2048 | 360° (wraps to 0) |
The original type-1 placement fallback and its message-group assignment are described in actor initialization.
MPOB exporter model lookup behavior:
- Primary source is
model_name(9 bytes, null-trimmed). - If
model_nameis empty, exporter falls back toRAANusingRAHDscript metadata (see RAAN). - If RAAN also yields no result,
script_nameis used as a last resort. - Example:
script_name = FAVISresolves via RAAN toFVPRA001.
MPSO uses a 12-byte model_name field (no fallback chain — model name is always present).
MPSO (Static Objects)
MPSO starts with a little-endian object count, followed by 66-byte records.
All fields little-endian.
| Offset | Size | Type | Name | Description |
|---|---|---|---|---|
| 0x00 | 4 | u32 | id | Object id |
| 0x04 | 12 | [u8; 12] | model_name | Model reference |
| 0x10 | 3 | i24 | pos_x | Position X |
| 0x13 | 1 | u8 | pad_x | Alignment byte |
| 0x14 | 3 | i24 | pos_y | Position Y |
| 0x17 | 1 | u8 | pad_y | Alignment byte |
| 0x18 | 3 | u24 | pos_z | Position Z |
| 0x1B | 1 | u8 | pad_z | Alignment byte |
| 0x1C | 36 | i32[9] | rotation_matrix | 3x3 Q4.28 rotation matrix |
| 0x40 | 2 | u8[2] | unused | Always 0. |
Matrix entries are row-major: row 0 is [m0, m1, m2], row 1 is
[m3, m4, m5], and row 2 is [m6, m7, m8]. Loading converts each signed
Q4.28 entry to a float in place without transposition or normalization.
The matrix maps local column vectors into world coordinates, before adding
the placement position. Camera transformation multiplies this matrix on the left.
MPRP (Rope Chains)
MPRP starts with a little-endian record count, followed by 80-byte records.
All fields little-endian.
| Offset | Size | Type | Name | Description |
|---|---|---|---|---|
| 0x00 | 4 | u32 | id | Rope/object id |
| 0x04 | 1 | u8 | reserved | Not read at runtime. |
| 0x05 | 3 | i24 | pos_x | Base position X |
| 0x08 | 1 | u8 | pad_x | Alignment byte |
| 0x09 | 3 | i24 | pos_y | Base position Y |
| 0x0C | 1 | u8 | pad_y | Alignment byte |
| 0x0D | 3 | i24 | pos_z | Base position Z |
| 0x10 | 4 | i32 | angle_y | Rope heading field |
| 0x14 | 4 | i32 | type | Type/discriminator |
| 0x18 | 4 | i32 | swing | Swing parameter |
| 0x1C | 4 | i32 | speed | Speed parameter |
| 0x20 | 2 | i16 | length | Number of rope links |
| 0x22 | 9 | [u8; 9] | static_model | Optional terminal model |
| 0x2B | 9 | [u8; 9] | rope_model | Link model name (for example ROPELINK) |
| 0x34 | 4 | i32 | sound_1 | Rope effect for low swing speed; 0 = none. |
| 0x38 | 4 | i32 | sound_2 | Rope effect for medium swing speed; 0 = none. |
| 0x3C | 4 | i32 | sound_3 | Rope effect for high swing speed; 0 = none. |
| 0x40 | 16 | i32[4] | unknown | Read at runtime; zero in all 29 shipped records. |
Rope instancing behavior:
- Decode base translation with the same MPOB scale/sign rules.
- Spawn
lengthcopies ofrope_model. Every link and the end model are yawed byangle_y(0..2047, 2048 per turn) with the same convention as an MPOB object’sangle_y; pitch and roll are zero for a rope at rest. - Link 0 is placed at the base position; each following link is
0.8lower in Y (the height of the link model, shippedROPELINKandCHAINLNKare both 0.8 tall and centered). - If
static_modelis present, place one additional instance0.8below the last link. - Rope swing sounds use
sound_1–sound_3; see positional sound.
Current parser behavior: MPRP is parsed into typed 80-byte records only when section payload is an exact fit for record_count; otherwise raw fallback is kept.
RALC (Location Data)
RALC contains scripted coordinate offsets for objects (e.g. the Boatman’s waypoints). Records are 12-byte entries.
All fields little-endian.
| Offset | Size | Type | Name | Description |
|---|---|---|---|---|
| 0x00 | 4 | s32 | offset_x | X coordinate offset (applied to MPOB translated position) |
| 0x04 | 4 | s32 | offset_y | Y coordinate offset |
| 0x08 | 4 | s32 | offset_z | Z coordinate offset |
Actor setup first stores the original runtime home position as location 0, then appends RAHD.ralc_count entries starting at RAHD.ralc_offset. Each appended location is the home position plus its signed RALC offsets, with wrapping 32-bit addition. Home coordinates are the original MPOB position multiplied by 256, before family-local conversion. Authored offsets already use runtime coordinate units. Locations belong to each actor instance. See location movement.
RAVC (VCollide)
RAVC uses 9-byte entries and appears only in a subset of maps (CATACOMB and DRINT — collision data for the dragon and golem).
All fields little-endian.
| Offset | Size | Type | Name | Description |
|---|---|---|---|---|
| 0x00 | 1 | i8 | offset_x | Local collision offset X |
| 0x01 | 1 | i8 | offset_y | Local collision offset Y |
| 0x02 | 1 | i8 | offset_z | Local collision offset Z |
| 0x03 | 2 | u16 | vertex | Model vertex index used as reference point for the collision sphere |
| 0x05 | 4 | u32 | radius | Collision sphere radius |
Current parser behavior: records are parsed as fixed 9-byte entries only when section payload is an exact fit; otherwise raw fallback is kept. RAVC is flat-out missing (not just empty) in RGM files without collision objects.
WDNM (Walk Node Map)
WDNM defines walk node maps for AI pathfinding. Count-prefixed: record count is the number of walk-map blocks.
All fields little-endian.
WalkMap Record
| Relative Offset | Size | Type | Name | Description |
|---|---|---|---|---|
| +0x00 | 4 | u32 | map_length | Payload byte length after this length word |
| +0x04 | 4 | u32 | node_count | Number of walk-nodes in this map |
| +0x08 | 4 | u32 | node_count_dup | Duplicate of node_count |
| +0x0C | 3 | s24 | map_pos_x | Map position X |
| +0x0F | 1 | u8 | pad_x | Alignment byte |
| +0x10 | 3 | s24 | map_pos_y | Map position Y |
| +0x13 | 1 | u8 | pad_y | Alignment byte |
| +0x14 | 3 | s24 | map_pos_z | Map position Z |
| +0x17 | 1 | u8 | pad_z | Alignment byte |
| +0x18 | 4 | u32 | radius | Map bounding radius |
| +0x1C | … | — | walk_nodes | node_count × WalkNode records |
The map length excludes its own four-byte length word. Its payload contains the remaining 24-byte map header and all length-prefixed nodes. Embedded maps are loaded in file order and prepended to the runtime map list, so traversal visits them in reverse file order.
WalkNode Record
| Relative Offset | Size | Type | Name | Description |
|---|---|---|---|---|
| +0x00 | 4 | u32 | node_length | Payload byte length after this length word |
| +0x04 | 2 | u16 | node_pos_x | Local position X |
| +0x06 | 2 | s16 | node_pos_y | Local position Y |
| +0x08 | 2 | u16 | node_pos_z | Local position Z |
| +0x0A | 1 | u8 | reserved | File value discarded; runtime storage holds node ownership. |
| +0x0B | 1 | u8 | route_count | Number of routes from this node |
| +0x0C | … | — | routes | route_count × NodeRoute records |
The node length excludes its own four-byte length word and equals
8 + 4 * route_count. Loading copies the packed node coordinates directly;
the map header’s position is not added to them.
The loader clears the reserved byte before using the node. Runtime reservation sets that byte to the low byte of the owning actor identifier. An unowned node or a node already owned by the requesting actor can be acquired; another actor’s reservation prevents acquisition. Releasing a reservation clears the byte only when the releasing actor still owns it. The periodic sound-maintenance reset clears all node ownership bytes regardless of owner.
Runtime node selection
WDNM maps load in file order and each new map is prepended to the runtime map list. Runtime traversal therefore visits maps in reverse file order. Nodes within each map retain their file index order. Map identifiers are assigned in load order independently of runtime list position.
The nearest-node query traverses the loaded map list and each map’s nodes in index order. It compares the sum of absolute X, Y and Z differences, using unsigned X/Z and signed Y from the node record. The query uses unscaled node coordinate units.
Only distances strictly below 4096 qualify. Equal distances retain the first candidate. When a new candidate has distance strictly below 12, the query returns immediately, even if a later node would be closer. Otherwise it returns the best candidate after traversing all maps. With no qualifying node, it returns no map and node index -1. Node reservations do not filter this selection query.
NodeRoute Record (4 bytes)
| Relative Offset | Size | Type | Name | Description |
|---|---|---|---|---|
| +0x00 | 2 | u16 | target_node_id | Destination walk-node index |
| +0x02 | 2 | u16 | cost | Route traversal cost |
RAHD (Actor Header)
RAHD is a count-prefixed section with 165-byte records. Each record provides per-actor metadata: script name, bytecode location in RASC, string/variable table pointers, animation references, collision data, and attribute hooks.
Section payload starts with a 4-byte LE record count, followed by 4 fixed bytes (1B 80 37 00), then count × 165 bytes of records. The engine reads the count and prefix separately, then bulk-reads count × 165 bytes as the record array. Offsets below are within each 165-byte record (starting at payload offset 8 + i × 165). The Rust parser in this repo starts records 4 bytes earlier (at 4 + i × 165) and adds 4 to all field offsets.
At load time, the engine converts most offset fields into absolute pointers by adding the corresponding section’s data pointer (rebasing). Offset 0x00 is overwritten with a linked-list next-pointer at runtime.
All typed fields are little-endian.
| Offset | Size | Type | Name | Description |
|---|---|---|---|---|
| 0x00 | 4 | — | field_00 | Overwritten at runtime (linked-list next-pointer) |
| 0x04 | 9 | [u8; 9] | script_name | Script/actor name, null-padded |
| 0x0D | 2 | u16 | instances | Number of instances for this actor |
| 0x0F | 2 | — | padding | Always 0. |
| 0x11 | 4 | i32 | instance_counter | Runtime instance counter (incremented during setup; advances variable_offset by num_variables × 4 per instance) |
| 0x15 | 4 | u8[4] | anim_speed | Read as individual bytes. Byte 0x15: frame limit — animation advances only while frame_counter < byte_0x15. Byte 0x16: frame increment added per tick. |
| 0x19 | 4 | u32 | ranm_offset | Byte offset into RANM section data (rebased to pointer at load) |
| 0x1D | 4 | u32 | raat_offset | Byte offset into RAAT section data (rebased to pointer at load) |
| 0x21 | 4 | i32 | raan_count | Number of RAAN entries for this actor |
| 0x25 | 4 | u32 | raan_data_size | Total byte size of this actor’s RAAN entries. Zero when raan_count = 0. |
| 0x29 | 4 | i32 | raan_offset | Byte offset into RAAN section data (rebased to pointer at load) |
| 0x2D | 4 | — | anim_control_prefix | Low byte stored at animation control struct offset +0x12 during RAGR loading. The complete signed field is compared with −1 when selecting the static-model shortcut; see model loading. |
| 0x31 | 4 | u32 | ragr_offset | Byte offset into RAGR section data (rebased to pointer at load) |
| 0x35 | 8 | — | padding | Always 0. |
| 0x3D | 4 | u32 | rafs_index | Index into RAFS data (vestigial — RAFS is 1 byte in all shipped files) |
| 0x41 | 4 | u32 | num_strings | Number of strings used by this actor’s script |
| 0x45 | 4 | — | padding | Always 0. |
| 0x49 | 4 | u32 | string_offsets_index | Byte offset into RASB section data |
| 0x4D | 4 | u32 | script_length | Byte length of this actor’s bytecode block in RASC |
| 0x51 | 4 | u32 | script_data_offset | Byte offset into RASC section data (rebased to pointer at load) |
| 0x55 | 4 | u32 | script_pc | Execution start address; rebased at load to script_data_offset + script_pc (absolute pointer) |
| 0x59 | 4 | — | anim_buffer_swap | Read as byte at 0x59. Boolean: non-zero triggers animation frame buffer swap (copies between offsets +0xCB and +0x108 in actor struct). Zero uses primary buffer only. |
| 0x5D | 4 | u32 | rahk_offset | Byte offset into RAHK section data (rebased to pointer at load) |
| 0x61 | 4 | i32 | ralc_count | Number of authored location offsets appended after the actor’s initial home location. |
| 0x65 | 4 | — | field_65 | Auxiliary actor-header data. |
| 0x69 | 4 | u32 | ralc_offset | Byte offset into RALC section data (rebased: ralc_data + (offset ÷ 12) × 12) |
| 0x6D | 4 | u8[4] | actor_flags | Read as individual bytes. Byte 0x6D: animation state ID loaded into a global during dialogue setup, compared against hook data at +0x247 for state matching. Byte 0x6E: item/equipment flag (toggled at runtime). Byte 0x6F: passed to animation/sound function. |
| 0x71 | 4 | u32 | raex_offset | Byte offset into RAEX section data (rebased to pointer at load) |
| 0x75 | 4 | u32 | num_variables | Number of local variables for this actor |
| 0x79 | 4 | u8[4] | visibility_flags | Read as individual bytes. Byte 0x79: visibility test bypass (non-zero = always visible, skip LOD culling). Byte 0x7B: LOD culling mode (0 = fixed distance threshold, non-zero = dynamic distance threshold). |
| 0x7D | 4 | u32 | variable_offset | Byte offset into RAVA section data (÷ 4 = variable array index) |
| 0x81 | 4 | u32 | variable_offset_dup | Runtime copy of variable_offset; advanced by num_variables × 4 per instance |
| 0x85 | 4 | u32 | anim_frame_data | Animation frame count or group index. Upper 16 bits used as count (× 11 bytes per frame for allocation). |
| 0x89 | 4 | i32 | bounds_primary | Primary MPSZ record index; −1 derives bounds from the model. |
| 0x8D | 4 | i32 | bounds_secondary | Secondary MPSZ record index; −1 = absent. |
| 0x91 | 4 | i32 | bounds_tertiary | Alternate MPSZ record index; −1 = absent. |
| 0x95 | 2 | i16 | combat_flag | Combat/state flag. |
| 0x97 | 2 | i16 | raex_stat | Stored at actor state +0x97 after SOUP function lookup. -1 = disabled. |
| 0x99 | 2 | i16 | reserved_99 | Always -1. Not read at runtime. |
| 0x9B | 2 | i16 | texture_id | Actor texture set. Equal to the RAEX texture_id of the same actor in every shipped file; zero when the actor has none. |
| 0x9D | 4 | i32 | ravc_offset | Byte offset into RAVC section data (rebased to pointer at load; -1 = none) |
| 0xA1 | 4 | i32 | ravc_count | Number of RAVC collision entries for this actor |
Total record size: 165 bytes (0xA5).
RAAN (Animation File References)
RAAN contains animation/model file path entries. Records are variable-length null-terminated strings with a 6-byte prefix.
Entry structure at a given byte offset (from RAHD raan_offset):
| Offset | Size | Type | Name | Description |
|---|---|---|---|---|
| 0x00 | 4 | u32 | reserved | Not read by any engine function. Skipped during entry iteration. |
| 0x04 | 1 | u8 | frame_count | Used as loop count for animation handle table entries. Capped at 255. |
| 0x05 | 1 | u8 | model_type | Type flag, converted to lowercase at load. Values: 0x63 (ASCII ‘c’) and 0x73 (ASCII ‘s’). |
| 0x06 | var | [u8] | file_path | Null-terminated file path string (e.g. 3dart\cyrsa001.3d) |
The engine iterates RAAN entries by skipping the 6-byte prefix, then scanning forward to the null terminator of file_path. The 4-byte dword at offset 0x00 is NOT used for seeking — the next entry is found purely by string scan.
The model name is extracted by stripping directory separators, file extension, and uppercasing the stem.
Model Fallback Resolution
When an MPOB record has an empty model_name, the exporter resolves a model via RAHD/RAAN:
- Look up
script_namein the RAHD index to get(raan_offset, raan_count). - Parse the RAAN entry at
raan_offsetto extract the file path. - Strip the path to a bare filename stem (e.g.
fxart\FVPRA001.3DC→FVPRA001). - Use the stem as the model name for asset lookup.
- If no RAHD/RAAN match, fall back to using
script_nameas the model name.
RAFS (Actor FSphere Data)
RAFS was intended to store pre-computed fsphere (combat/collision sphere) data for actors. The engine only loads this section when its size exceeds 10 bytes. In all 27 shipped RGM files, RAFS is exactly 1 byte — the section is vestigial and never loaded at runtime. FSpheres are instead computed from MPSZ bounding volume records indexed via RAHD.
RAST (String Data)
RAST contains all script string literals as null-terminated strings concatenated into a single blob. No count prefix; it is a flat byte array. Individual strings are located by offsets stored in RASB.
During loading, RASB offsets are rebased by adding the RAST data pointer, converting relative offsets into direct pointers.
RASB (String Offset Table)
RASB contains u32 LE offsets into RAST, one per string per actor. Each actor’s portion starts at string_offsets_index (from RAHD) and contains num_strings entries.
| Offset | Size | Type | Name | Description |
|---|---|---|---|---|
| +0x00 | 4 | u32 | string_offset | Byte offset into RAST where the null-terminated string begins |
Total section length: sum of all actors’ num_strings × 4.
RAVA (Local Variables)
RAVA contains initial values for local script variables as a flat array of i32 LE integers. The first 4 bytes are always zero (sentinel).
Each actor’s portion starts at byte offset variable_offset (from RAHD; divide by 4 for the array index) and contains num_variables entries. When an actor has multiple instances, variables are replicated instances times. Variables are addressed by index in script bytecode (opcode 0x0A).
RASC (Script Bytecode)
RASC contains compiled SOUP386 scripting bytecode as a contiguous byte blob. Per-actor instruction blocks are located via RAHD offsets (script_data_offset, script_length, script_pc) and executed by the SOUP virtual machine.
For VM architecture, opcode encoding, operator tables, and execution semantics, see SOUP Scripting. For the definition-file format, see SOUP386.DEF.
Section Layout
| Region | Size | Description |
|---|---|---|
| Preamble | script_data_offset bytes | Zero-padded address space used by rebased hook/offset references |
| Script blocks | remainder | Concatenated per-actor bytecode blocks in RAHD order |
Total payload = first actor script_data_offset + sum of all actors’ script_length values. For each actor, execution starts at script_pc relative to that actor’s block.
Other RA* sections provide separate actor data; their layouts and offset bases
are section-specific.
RAHK (Hook Data)
RAHK contains native call records. An actor’s nonzero RAHD.rahk_offset is a
byte offset into this section’s payload; the loader adds the RAHK payload base.
Zero means no hook. The ordinary actor script wrapper dispatches the referenced
record through the native function table, separately from its RASC entry and
main continuations.
A stored record begins with a little-endian u16 native function index and a
u8 argument count, followed by five-byte encoded arguments. Each argument has
a one-byte parameter-mode tag and four operand bytes. Function indices use the
installed SOUP definition table. Parameter evaluation uses the receiving actor
for local-variable references and the executing actor’s string context.
The native hook is not a table of RASC offsets and has no group-indexed array. Its execution gates are described in SOUP ordinary update gates.
RAEX (Extra Data)
RAEX contains per-actor extra data with 30-byte fixed-size records (15 × i16 LE fields). The engine requires this section during loading. Record count is section_data_length ÷ 30. Per-actor RAEX offsets are stored in RAHD (raex_offset).
Field names Grip0 through RangeMax are from the in-game debug console.
All fields little-endian.
| Offset | Size | Type | Name | Description |
|---|---|---|---|---|
| 0x00 | 2 | i16 | grip0 | First source-model vertex for the drawn-weapon attachment. |
| 0x02 | 2 | i16 | grip1 | Second source-model vertex for the drawn-weapon attachment. |
| 0x04 | 2 | i16 | scabbard0 | First source-model vertex for the stowed-weapon attachment. |
| 0x06 | 2 | i16 | scabbard1 | Second source-model vertex for the stowed-weapon attachment. |
| 0x08 | 2 | i16 | anim_frame_ref | Matches RAHD anim_frame_data at 0x85. Set on mobile actors only. |
| 0x0A | 2 | u16 | texture_id | Actor texture set (TEXBSI number) for skin variants; zero selects the models’ own textures. See actor texture sets. |
| 0x0C | 2 | i16 | v_vertex | Model vertex used by combat contact queries. |
| 0x0E | 2 | i16 | v_size | Combat contact radius in model units; copied to runtime Q8 units. |
| 0x10 | 2 | i16 | taunt_id | First taunt animation id; additional taunts count up from this value. |
| 0x12 | 2 | i16 | collision_sphere_scale | Eight-fractional-bit radius scale for actor collision spheres; nonpositive values select 256. |
| 0x14 | 2 | i16 | field_14 | Source value for RAHD raex_stat at 0x97. Set on combat actors. |
| 0x16 | 2 | i16 | field_16 | Set on some combat actors. |
| 0x18 | 2 | i16 | range_min | Combat engagement minimum range. Multiply by 256 for world units. Only set on dragon, golem, serpent. |
| 0x1A | 2 | i16 | range_ideal | Combat ideal range. Same scaling. |
| 0x1C | 2 | i16 | range_max | Combat maximum range. Same scaling. |
Total record size: 30 bytes (0x1E). Record count is section_data_length / 30.
Actor texture sets
A nonzero RAEX texture_id applies to every multi-frame model the actor draws,
including each RAAN frame-source model. The engine keeps one copy of a frame-source
model per texture set and shares it between actors with the same set. Each face
texture word of the copy is bank << 7 | image; words whose bank is greater than 1
take the actor’s texture set as their bank and keep their image index. Words with
bank 0 or 1 are unchanged. Geometry, frame data and normals are those of the source
model.
RAAT (Attribute Data)
RAAT contains per-actor attribute tables. Each actor has a 256-byte attribute block, ordered sequentially (actor 0 at offset 0, actor 1 at offset 256, etc.).
Attribute names are defined in the auto…endauto section of SOUP386.DEF (see SOUP386.DEF). Each byte is a named attribute value; zero means unset. Attributes are read/written by the script functions GetAttribute and SetAttribute.
Total section length: record_count × 256.
RAGR (Animation Groups)
RAGR contains animation group definitions that link actors to their animation data in RAAN. RAGR provides the RGM-embedded equivalent of the AIAN section found in standalone .AI files; the engine selects one or the other source based on a runtime mode flag.
Per-actor RAGR data is located via RAHD.ragr_offset (offset 0x31). Entries are size-prefixed: the first u16 is the entry payload size (excluding itself); a value of 0 terminates the list. Advance to the next entry: current_position + 2 + entry_size.
The prefix byte used by the AIAN (standalone .AI) path is NOT present in RGM RAGR — instead, that value comes from RAHD offset 0x2D.
Animation Group Entry
All fields little-endian.
| Relative Offset | Size | Type | Name | Description |
|---|---|---|---|---|
| +0x00 | 2 | u16 | entry_size | Payload size in bytes after this field. 0 = end of groups. Should equal 8 + frame_count × 3. |
| +0x02 | 2 | u16 | group_index | Animation group slot (0–177; validated ≤ 0xB1 at load) |
| +0x04 | 2 | u16 | anim_id | Animation identifier |
| +0x06 | 2 | u16 | anim_type | Animation type (only low byte used). Values: 0 = interruptible (idle/panic), 1 = must complete (combat), 2 = no panic revert (ledge-hang loops). |
| +0x08 | 2 | u16 | frame_count | Number of animation frames in this group |
| +0x0A | var | [u8; frame_count × 3] | commands | Packed 3-byte animation commands, one per frame |
Animation command (3 bytes, packed LE)
The low four bits select the opcode. A file ShowFrame command, opcode zero,
uses all remaining twenty bits as an unsigned frame reference. It does not
encode an attachment vertex.
RAAN entries form one concatenated frame table in file order. An entry with frame count N contributes its model’s frames zero through N−1. The frame reference selects one entry in that table. For example, model A with three frames followed by model B with two frames gives references 0–2 for A and 3–4 for B. Reference 4 selects model B, frame 1.
The loader rewrites each ShowFrame into two ten-bit fields: runtime model
handle and frame index. These patched runtime commands have a different
meaning from file commands. Both resolved fields fit in a byte. Raw file
commands must be resolved through RAAN before they can be played.
Opcodes 4 (PlaySound) and 10 (ChangeAnimGroup) use two ten-bit fields in the file. Their parameters are sound settings and animation jump targets, respectively. See animation playback and the opcode table.
RANM (Namespace)
RANM contains object namespace strings used for cross-script object references. Each actor’s portion is located by the RAHD ranm_offset field at 0x19. The extracted string provides the actor’s canonical name for ObjDot* opcodes using selector byte 4 (named object from string table).
MPSL (Lights)
MPSL starts with a little-endian record count, followed by 42-byte records.
All fields are little-endian.
| Offset | Size | Type | Name | Description |
|---|---|---|---|---|
| 0x00 | 4 | u32 | link | Serialized linkage field, replaced when the light enters a spatial list. |
| 0x04 | 4 | u32 | world_id | World ID used to select the light at load time. Reused as linkage after selection. |
| 0x08 | 4 | i32 | pos_x | X position in engine coordinates. |
| 0x0C | 4 | i32 | pos_y | Y position in engine coordinates. |
| 0x10 | 4 | i32 | pos_z | Z position in engine coordinates. |
| 0x14 | 2 | i16 | radius | Local-light radius in engine coordinates. |
| 0x16 | 2 | i16 | field_16 | Auxiliary field. |
| 0x18 | 6 | [i16; 3] | rgb | Signed red, green and blue strengths. Negative channels subtract light. |
| 0x1E | 1 | u8 | field_1e | Auxiliary byte. |
| 0x1F | 1 | u8 | active | Must equal 1 for load-time selection. |
| 0x20 | 6 | [u8; 6] | field_20 | Auxiliary bytes. |
| 0x26 | 4 | [u8; 4] | field_26 | Auxiliary bytes. |
With map lights enabled, the loader selects records whose active byte equals
1 and whose world_id matches the selected WORLD.INI entry. It inserts those
records into spatial lists and replaces the first two words with list links.
The position, radius and RGB fields retain their file values. The first three
bytes are not a color, and the RGB words are not a direction vector.
Local-light selection for each model is described in model lighting.
MPMK (Markers)
MPMK starts with a little-endian record count, followed by a contiguous array of
12-byte position records, then a separate array of one auxiliary byte per marker.
The payload length is 4 + count * 12 + count. The arrays are not interleaved.
All fields little-endian.
| Offset | Size | Type | Name | Description |
|---|---|---|---|---|
| 0x00 | 3 | i24 | pos_x | Position X |
| 0x03 | 1 | u8 | pad_x | Alignment byte |
| 0x04 | 3 | i24 | pos_y | Position Y |
| 0x07 | 1 | u8 | pad_y | Alignment byte |
| 0x08 | 3 | i24 | pos_z | Position Z |
| 0x0B | 1 | u8 | pad_z | Alignment byte |
Position fields use the same i24+pad encoding as MPOB/MPSO. No explicit record ID field.
The auxiliary table begins at 4 + count * 12; byte i belongs to position
record i. A lookup in a loaded world requires the auxiliary byte to equal 1
and the index to be below the marker count; a zero byte identifies an unavailable
marker. All 27 shipped maps hold at most 49 markers and use only the values 0
and 1.
See node-marker lookup. The metadata exporter retains it
as reserved for compatibility. map_position retains the three signed map
coordinates before export-axis conversion and scaling; position retains the
existing converted coordinates. Runtime placement can use map_position without
recovering integers from floating-point export coordinates.
MPSZ (Bounding Volumes)
MPSZ is an array of 49-byte bounding volume records used to build per-actor fspheres (combat/collision bounding spheres) at runtime. Not count-prefixed — record count is section_size / 49. RAHD fields at 0x89, 0x8D and 0x91 store per-actor indices into this table. A primary index of −1 derives bounds from the model; −1 in either alternate index means absent.
Record Layout (49 bytes)
| Offset | Size | Type | Name | Description |
|---|---|---|---|---|
| 0x00 | 1 | u8 | flags | Initially zero; runtime sphere construction selects the dominant axis here |
| 0x01 | 4 | i32 | total_x | Total extent X (pos_x + neg_x) |
| 0x05 | 4 | i32 | total_y | Total extent Y (pos_y + neg_y) |
| 0x09 | 4 | i32 | total_z | Total extent Z (pos_z + neg_z) |
| 0x0D | 4 | i32 | center_x | Center offset X |
| 0x11 | 4 | i32 | center_y | Center offset Y |
| 0x15 | 4 | i32 | center_z | Center offset Z |
| 0x19 | 4 | i32 | pos_x | Positive half-extent X |
| 0x1D | 4 | i32 | pos_y | Positive half-extent Y |
| 0x21 | 4 | i32 | pos_z | Positive half-extent Z |
| 0x25 | 4 | i32 | neg_x | Absolute negative half-extent X |
| 0x29 | 4 | i32 | neg_y | Absolute negative half-extent Y |
| 0x2D | 4 | i32 | neg_z | Absolute negative half-extent Z |
Invariant: total = neg + pos for each axis. Center is always zero in shipped files. Bounds can be asymmetric (neg != pos).
At runtime, the engine copies these 49 bytes and calls the fsphere builder to create a 3D bounding volume for the actor. RAHD provides three index fields per actor, allowing reference to different bounding records. Only a subset of actors have direct RAHD indices.
Flags are zero in shipped files. The flag byte precedes the integers; placing it last shifts every decoded field.
ISLAND CYRUS references primary record 120 and secondary record 121, with no tertiary record. Its primary totals are (48, 128, 49), positive extents (24, 62, 11), and negative extents (24, 66, 38).
MPSF (Flat Objects)
MPSF starts with a little-endian record count, followed by 24-byte records. Each record places a textured quad in the scene.
All fields little-endian.
| Offset | Size | Type | Name | Description |
|---|---|---|---|---|
| 0x00 | 4 | u32 | id | Object id |
| 0x04 | 4 | i32 | reserved | Not read at runtime. |
| 0x08 | 3 | i24 | pos_x | Position X |
| 0x0B | 1 | u8 | pad_x | Alignment byte |
| 0x0C | 3 | i24 | pos_y | Position Y |
| 0x0F | 1 | u8 | pad_y | Alignment byte |
| 0x10 | 3 | u24 | pos_z | Position Z |
| 0x13 | 1 | u8 | pad_z | Alignment byte |
| 0x14 | 2 | u16 | texture_data | Packed: texture_id = data >> 7, image_id = data & 0x7F |
| 0x16 | 2 | i16 | reserved | Not read at runtime. |
Position decode uses the same MPOB scale/sign rules. MPSF records are submitted
as camera-facing textured quads centered on their placed position. Their full
width and height in engine units are the source BSI image dimensions multiplied
by the BSI texture scale divided by 256. A zero texture scale selects 256.
The ordinary flat path uses geometry scale 1 and copies the camera orientation;
it does not use a fixed-size model quad or an authored object rotation.
Texture-coordinate extents are computed separately from geometry size. For
unreduced textures, they cover the complete source image, excluding upload
padding. The first two corners use the image top edge, and the next two use
the bottom edge. The
FLAT attributes below modify the submission flags and alpha.
FLAT
FLAT stores per-texture drawing attributes, separate from the placed objects
in MPSF. Its payload begins with 512 little-endian u16 record counts,
one for each texture archive ID. The records follow in ascending archive order.
Each record is three bytes: image ID, drawing flags, and alpha byte. Archives
with a zero count contribute no records. There is no separate total-count word.
Texture lookup splits the packed texture reference into an archive ID and seven-bit image ID, then searches that archive’s records for the first matching image. A missing archive or image has no attribute override.
For an MPSF draw, the base flags are 1 and the default alpha input is 255.
A matching attribute record is ORed into the flags and replaces the alpha input
with alpha_byte + 1, computed without byte wrapping. Thus a stored zero
supplies 1, and a stored 255 supplies 256. The geometry scale and texture
coordinate scale inputs remain 1. The base flag selects a centered quad;
attribute flags do not replace that anchor selection.
FLAT appears in all 27 shipped RGM files as the last section before END .
Its payload is 1111 bytes in 26 files and 1108 bytes in HIDEOUT.RGM.
External References
- UESP: Mod:RGM File Format
- UESP: Mod:Redguard File Formats
- RGUnity/redguard-unity
RGRGMFile.cs— RGM section parser - RGUnity/redguard-unity
RGRGMScriptStore.cs— RASC bytecode interpreter with dispatch loop and flags table (369 entries) - RGUnity/redguard-unity
soupdeffcn_nimpl.cs— SOUP function ID-to-name table (367 functions) - Dillonn241/redguard-mod-manager
ScriptReader.java— RASC bytecode disassembler (bytecode to readable script text) - Dillonn241/redguard-mod-manager
ScriptParser.java— RASC bytecode assembler (script text to bytecode) - Dillonn241/redguard-mod-manager
MapFile.java— RGM section reader/writer with chunk tag list - Dillonn241/redguard-mod-manager
MapHeader.java— RAHD record parser with field offsets - Dillonn241/redguard-mod-manager
MapDatabase.java— SOUP386.DEF parser (function, flag, reference, attribute definitions)