# Putting another model on the player Written 2026-10-12 (the user: "this model might be changed not decided yet so all those animations and stuff needs to be saved it would be easy to paste it over to the new models"). The player's body is Paragon Twinblast today. Everything that belongs to that one model is in **one data file**, `Config\DescentBody.json`; the code and the animations refer to roles. ## The three steps 1. **Add a profile** to `Config\DescentBody.json` under `"profiles"` (copy Twinblast's and change it): | Field | What it is | |---|---| | `mesh` | the skeletal mesh, `/Game/...` | | `yaw_deg` | the mesh component's yaw so the model faces the character's forward. A model that faces +Y (the usual one): -90 | | `scale` | puts the eyes of the standing pose at the camera (1.65 m). Start with 1, see "Calibrate" below | | `anim_folder` | where this model's baked animations go, e.g. `/Game/Player/NewModel/Anims` | | `eye_socket` | a socket (or bone) at the eyes, only used to measure the eye height | | `hide_bones_own_view` | bones not drawn from the player's own eyes, each with everything below it: at least the head and the neck | | `bones` | the model's bone name for each role: `pelvis`, `spine` (the FIRST spine bone), `chest`, `neck`, `head`, `upperarm_r/l`, `lowerarm_r/l`, `hand_r/l`, `knee_l/r` (the calf bone), `foot_l/r` | | `material_overrides` | optional, material slot name -> our material | | `gun_slots` | optional, material slots not drawn unless `descent.Body.Guns 1` | | `legacy` | optional, the model's OWN animations for `descent.Body.AnimSet 0`. Leave it out if it has none | 2. **Bake the animations onto it:** set `"model"` to the new name (or the environment variable `DESCENT_BODY_MODEL`) and run `Tools\import_assets.ps1 -Only body_anims` (about 30 s). It retargets every row of `"animations"` from the UE5-mannequin source clip onto the new skeleton and saves `A_Body_` sequences and `BodyAnims.measured.json` (clip speeds, footfalls) into the profile's `anim_folder`, and the IK rigs into its sibling `Retarget` folder. 3. **Flip the setting:** `"model"` in the file (or, in a running game, `descent.Body.Model ` before the character is set up). Then run `Tools\run_tests.ps1 -Filter Descent.Body` and `Tools\run_scenario.ps1 -Name player_body`: test `Descent.Body.ProfileRunSwim` says what the profile or the bake is missing; the Twinblast-only checks of `Descent.Body.Setup` (pack paths, the leg material) will have to be loosened. ## What carries over by itself * **The animation table** (`"animations"` in the file): which clip plays in which situation, loop / in place / reversed / upper body only. One table for every model. To change a clip for everybody, change its row and bake again. * **The baked clips' numbers**: play rate matched to the ground (the speed the clip's legs cover the ground at), the view bob in step with the footfalls. Measured per model by the bake, read from `BodyAnims.measured.json`. * **Everything made in code**, because it works on bone roles: the body anchored under the camera, the lean when looking down, the calmer upper body in the own view, the lower body turned toward the running direction, the two-bone IK of the jack-in and of the swim arms, the camera-space hand places of the swim. Also the state machine, the switches (`descent.Body.*`), the tests of the pure logic and the scenarios' probes. ## What has to be redone for each model * **Bone names** (`bones`) and **bones to hide** from the own view. Look down at -80 degrees in `player_body` (`fp_look_down_80`): no stump, no inside faces, no collar across the picture. Twinblast needed its four front collar bones hidden. * **Calibrate `scale`:** `player_body` logs `Eyes_Position socket ... cm above the feet`; scale = old scale x 165 / that number. * **Materials and guns:** `material_overrides` and `gun_slots` are Twinblast's. `Tools\ue\assets_13_twinblast.py` (the machine-leg material) reads Twinblast's textures and is not general. * **Packaging:** `Config\DefaultGame.ini` cooks the mesh's folder by name (`+DirectoriesToAlwaysCook=...TwinBlast/Meshes`), because the code loads the mesh by its path and no map references it. Point that line at the new mesh's folder, or the packaged game (`Tools\package.ps1`) has no body. The editor, the tests and the scenarios do not notice, since they read the project's own content. * **The retarget, if the engine cannot name the bones.** The bake uses `IKRigController.apply_auto_generated_retarget_definition`, which knows UE4/UE5-mannequin-like skeletons. For another rig, make `IK_` by hand in the profile's `Retarget` folder (the script uses an existing rig... and re-applies the automatic definition: change `_make_rig` to skip that for a hand-made one). * **Tuning that was done by eye for Twinblast** in `Core\Tuning\DescentTune_Body.h`: neck 12 cm behind the eye, the 30 degree look-down lean, the swim's neck 15 cm under the eye, the arm reach of 47 cm, the swim hands' places (they assume an arm about 51 cm long). A model of a different build needs another look at the `player_body` sheets. * **Assumed of every model:** it faces +Y with +X to its left and +Z up in its own space (the lean, the turns and the elbow hints use those axes); the root bone is bone 0 and stands on the floor between the feet; lower arm is the child of upper arm and hand of lower arm. ## Where things are | What | Where | |---|---| | Profile and table | `Config\DescentBody.json` | | Reader, the struct `FDescentBodyProfile` | `Source\TheDescent\Player\Body\DescentBodyProfile.h/.cpp` | | Bake | `Tools\ue\assets_14_body_anims.py` (module of `Tools\import_assets.ps1`) | | Baked clips, measurements | `Content\Player\\Anims\A_Body_.uasset`, `BodyAnims.measured.json` | | Behaviour and its numbers | `Player\Body\DescentBodyAnim.*`, `DescentPlayerBodyComponent.*`, `Core\Tuning\DescentTune_Body.h` | | Compare run clips | scenario `body_run_look` (add a row with a new role, bake, add a variant) |