Knowledge base

V's body in motion: the preview puppets, their idles and the deformation rig

Research in progress: some of this may be wrong. These notes record what XF Studio’s research has worked out so far about how Cyberpunk 2077 and its modding frameworks behave. Most of it comes from reading source code and game resources offline; not all of it has been confirmed in the running game, and claims graded hypothesis are unproven. If you spot a mistake or know more, please open an issue or suggest an edit.

Evidence grades

source
Read in engine/framework/tool source or decompiled data.
resource
Observed in extracted game or mod resources.
wiki
Documented in the Modding Docs, text or image.
runtime
Observed in the running game.
hypothesis
Not yet established.

Maturity: Draft. Consolidated on 27 September 2026 from the installed 2.31 game (the REDmod TweakDB sources, the player and preview entities, player_paperdoll.animgraph, ui_female.anims, woman_base.rig, woman_base_deformations.rig and .animgraph, the body, feet and nails resources), the decompiled scripts, WolvenKit 9.0.1 and the Studio's own offline measurements on the reference profile's V. Nothing here has runtime evidence yet. Grades follow the knowledge rules: source engine, framework or tool source or decompiled scripts, resource extracted game or mod resources, wiki Modding Docs, runtime running game, [offline] our own tool runs and measurements, hypothesis not yet established. Hashes, commands and the numbers behind each claim are in the deformation rig evaluator evidence.

This page answers which puppet shows V in the creator and the inventory, which idles those screens play on her body, how the game moves the body's helper joints under any pose, and why a clip belongs with a feet state. How the body is assembled is in body rendering; photo-mode poses in poses; the face's idles in facial expressions.

1. The preview puppets

ScreenTweakDB recordFemale entityAppearanceGrade
Character creatorCharacter.Player_Puppet_Menubase\characters\entities\player\player_wa_tpp.entcharacter_creationresource REDmod player_menu_record.tweak
InventoryCharacter.Player_Puppet_Inventoryep1\characters\entities\player\player_wa_tpp_ep1.ent ([EP1])as V is dressedresource REDmod player_inventory_record.tweak
Gender selection(the creator's first screen)base\gameplay\gui\fullscreen\main_menu\gender_selection_female.ent—resource listed in the creator's UI resources; its puppet setup unread

player_wa_tpp.ent has three animated components resource: root (woman_base.rig, player_paperdoll.animgraph, with ui_female.anims through TPP Player Animation Setup), deformations (woman_base_deformations.rig and .animgraph, its control binding naming root) and breasts (woman_base_breasts.rig and .animgraph, bound to deformations). The photo-mode puppet has the same deformations component (poses §4).

2. The preview idles

The paperdoll body graph picks a clip per screen from AnimFeature_Paperdoll, which the preview controllers set from the camera slot [source: entityPreviewGameController.script]. Its state machines and screen switches loop these clips of ui_female.anims resource:

Studio entryClip (body)LengthGraph state and flagsFace loopFeet it's authored for
Creator close-upui_closeup_shot12.33 sstate machine closeup (characterCreation_Head, _Eyes, _Nose, _Lips, _Jaw, _Hair pick one-shots from it)ui_closeup_shot (22.07 s)the creator puppet's lifted feet
Creator close-up, eyes sectionui_closeup_shot12.33 scloseup with characterCreation_Eyes set (the eye camera: eyes, eyebrows, lash colour, eye makeup rows)the face graph's one-shot ui_closeup_shot_eyes (4.00 s) once, then ui_closeup_shot (facial animation §6)lifted
Creator full bodyui_fullbody_shot15.73 sstate machine fullbody (after the one-shot ui_closeup_to_fullbody)ui_fullbody_shot (22.07 s)lifted
Creator nailsui_expose_hand_loop4.70 sstate loop (characterCreation_Nails: ui_expose_hand_start → loop → _end)none in the face setlifted
Gender selectionui_gender_selection15.63 splayed directly by the screens' switchnone in the face setnot established
InventoryUI_full_shot11.07 sstate Idle (inventoryScreen; _Legs and _Chest play ui_check_pants / ui_check_jacket once)ui_closeup_shot after the one-shot ui_inventory_pickup (facial expressions §5)V's own (flat when barefoot)
  • One-shots, not idles: ui_check_boots, ui_check_pants, ui_check_jacket, ui_closeup_to_fullbody, ui_fullbody_to_closeup, the section showcases, ui_expose_hand_start and _end resource.
  • Weapon idles: 53 looping clips (handgun_idle, katana_idle, …) play in the inventory's weapon view with the held weapon resource; the Studio draws no weapon, so it doesn't offer them.
  • ui_expose_teeth is in the set but not in the body graph resource.
  • The inventory idle is the relaxed standing loop the in-game inventory shows: the hips turned about 40° from the camera, the head turned back towards it by 0 to 12° as the weight shifts, the left foot turned out [offline: UI_full_shot sampled at seven phases].
  • Their formats resource: ui_closeup_shot, ui_expose_hand_loop and ui_gender_selection are SIMD clips (animAnimationBufferSimd: every frame of every joint, rotations quantised to 16 bits, laid out four joints at a time); ui_fullbody_shot and UI_full_shot are compressed clips like the photo-mode poses. woman_base.rig keys all of them; WolvenKit's clip export gives the skeleton's nodes the rig's A pose (aPoseLS), not its reference pose [offline: within 6 × 10⁻⁸].
  • What the Studio used before was the close-up body clip on every framing, including the whole-body view [source: the Studio].

How the Studio reads them

Natively, on both hosts, with no Python [source: the Studio, 28 September]. The idle host (idle-host.ts) reads the third-person player entity (PLAYER_ENTITIES, with its ArchiveXL patches), takes its root animated component's graph (player_paperdoll.animgraph) and animation sets, finds the looping clip of each preview screen in the graph (previewIdles in idle-body.ts: the rule tools/prepare_body_idles.py applied), decodes each clip from the first set that holds it (anim-set.ts, SIMD and compressed), and serves each idle's body as every frame on the rig (GET /api/idles?body=<id>, a xfs/pose-sample-1 record). The page builds the skeleton from the rig's A pose and plays the frames as it plays a moving pose (platform/scene/idle-source.ts). The graph holds blend curves the reader doesn't decode, so it is read leniently: those properties are left out (the answer names them), and nesting up to 4,096 levels is allowed for its node chain. What was read is cached under <resolver cache>/idles/ by the installation's fingerprint; a warm start answers without opening the game. The head's joints follow Head through the face skeleton's own parents (FACE_SKELETON, read natively).

  • The oracle [offline: tools/native-idle-oracle.ts, game 2.31, direct route]: against the Python preparation (WolvenKit's export), the catalogue is identical (6 entries, the 53 weapon idles left out alike), the 345-joint ancestry identical, and for all five body clips every exported key agrees within 1.1 × 10⁻⁷ m and 2.1 × 10⁻⁶ (quaternion component), every joint's world position within 0.004 mm and rotation within 0.0006° at every frame the page plays, and the rest within 6 × 10⁻⁸. The same holds on the reference MO2 route. Rendered side by side in the browser (the face's motion off on both), the native and Python idles differ by more than 8 of 255 levels on at most 0.01 % of the 3D view's pixels at 0, 3, 6.5 and 10 s [offline: tools/idle-source-look.ts, private captures]. The opt-in test tests/native-idle-oracle.test.ts holds those tolerances. Reading the idles takes about 0.2 s once the route is open, a body 60 ms; a warm start reads the disk cache in 5 ms [offline].
  • The face is native too [source: the Studio, 28 September]. Each idle's face clip is read from the creator puppet's face set and solved on the host by XF Studio's own facial solver when the idle is first chosen (GET /api/idles?face=<id>, a xfs/face-motion-1 record; facial animation §5); the blink and the live Expressions face are solved the same way. No Python on either host. XFS_IDLE_SOURCE=prepared (with XFS_PREPARED_MOTION=on) plays the Python preparation instead, as the oracle.
  • Poses play on the same rig, so they now work wherever the idle does, the desktop app included.

3. The deformation rig

The main rig (woman_base.rig, 71 joints) is what every body clip keys. Every body mesh is also skinned to some of the 114 helper joints (deltoids, latissimus, scapula and chest muscles, twist joints of the thighs, calves, upper arms and wrists, knuckles, elbows, buttocks, groin, knees) that no clip keys resource. woman_base_deformations.rig holds them (181 joints) with their parents: the latissimus, scapula and chest joints under Spine3, the knee and calf joints under LeftLeg, the elbow and wrist joints under LeftForeArm resource. The body meshes are bound in the rig's aPoseMS (within 0.26 mm and 0.8°); its boneTransforms are another pose [offline].

The graph is one linear chain of 420 nodes: the reference pose and the shared main pose, into model space, then the constraints, then back to local space resource. The chain's node kinds and what the Studio infers they compute [hypothesis, checked as below]:

NodeCountComputes
StackTransformsExtender / …Shrinker7 eachAdds temporary transforms (_GRP), each at its parent times its reference offset; the shrinker removes them
TwistConstraint10The twist of B relative to A about A's front axis; each output joint turns by positiveScale or negativeScale of it about its own axis (the thigh keeps 10 % of the thigh's twist near the hip; the wrist joints take 37, 65 and 73 % of the hand's)
PointConstraint46The weighted mean of its sources' positions (preprocessedWeights); rotation kept
OrientConstraint31Its sources' rotations blended in turn by preprocessedWeights; position kept
AimConstraint_ObjectUp / _ObjectRotationUp78 / 14Turns forwardAxisLS at the target, upAxisLS towards the up object (or the up object's rotated upTransformVector)
SetBoneTransform72The joint at its source times the offset (WholeTransform)
SimpleSpline44The joint on the quadratic curve start → middle → end at defaultProgress, or at a float track's value
SimpleBounce90A measure: X of the end in the start's space; offset + (multiplier or negativeMultiplier) × X, written into each output's channels (position, Euler rotation in degrees or scale, each scaled) in its parent's space, or into a float track; a rotation measure reads degrees too. It drives muscle bulges and slides, not only jiggle; its delay and smoothing are dynamics
TranslationLimit8Clamps the joint's position in its parent's space

The check: with the main joints in the A pose, these semantics give back the A pose for 179 of the 181 rig joints within 0.5 mm and 1°, and for the other two (l/r_deltoid_top_bot_out_JNT) within 2°. That holds whatever the helper joints held on input, so every helper joint is fully determined by the graph [offline]. The twist sign, the bounce slopes and the spline's curve only act away from the A pose, where no capture has checked them yet.

Rotation channels are degrees [offline; hypothesis until capture ask 2]: the only bounces that write rotations are the elbow correctives (l/r_elbow_bend_A/B_mscl_JNT at −200 and l/r_elbow_back_mscl_JNT at −300 per metre of the elbow's bend measure, RotZ). The measure is about 5–25 mm in bent-elbow poses, so the turn is 1–7° in degrees but 60–400° in radians, while the same measure scales those muscles by only 0.9 to 1.2. Read as radians they turned the elbow inside out under every bent-elbow pose (PREV-149); the A-pose check can't tell the units apart, because at rest the measure is on the zero slope. The engine's EulerAngles are degrees throughout.

The breasts rig is a simulation (dangles, drag, collision ellipsoids, blends and a static switch on a visual tag) resource; it isn't a constraint chain and the Studio leaves it out. At rest its joints keep the pose the deformation rig gives them hypothesis.

How the Studio uses it [source: the Studio]:

  1. The host reads the player entity (PLAYER_ENTITIES, with every ArchiveXL patch) and takes each animated component bound to another animated component. It compiles each rig and graph, read natively, into a program (deformation-rig.ts) and serves it beside the character record (rigs).
  2. The idle (idle-animation.ts) runs each program on the clip's pose every frame, about 0.3 ms. It drives each joint a program solves through a virtual driver. Without a program, helper joints fall back to the rig segment nearest them (nearestDriver). The drawn parts' dangle components (hair with physics) come after the rigs: their chain joints follow their own rig parents, or their simulation with Hair physics on (hair physics §4), and never fall to nearestDriver.
  3. Poses play on the same rigs. A photo-mode pose chosen in the Poses panel is a body clip on the idle's rig (src/pose-clip.ts, poses §9), so each program runs on the posed joints exactly as on an idle's. Strong poses are where the inferred semantics away from the A pose (twist sign, bounce slopes, the spline's curve) first show; capture ask 2 checks them.
  4. What it fixes: under the nearest-segment fallback, the latissimus, scapula and chest-side joints turned with the upper arm, so lowering the arms from the bind pose pulled the torso's sides in under the ribs. With the rig, the vertices mostly weighted to them move in by 11 mm on average in the close-up idle, against 38 mm before (17 against 44 in the inventory idle) [offline: the reference V's body posed both ways].

4. Feet states and the idles

The creator resource has two feet groups, flat_feet (the garment mesh l0_000_pwa_base__cs_flat.mesh) and lifted_feet (the body's own chunks 5–7 through l0_000_base__full.app) resource. The flat mesh is modelled flat, with its sole near the floor, around a skeleton whose foot is raised: the ankle stands 11.8 cm above the floor in the A pose. The lifted feet are modelled with the heel 5 to 8 cm up resource. So a clip suits one feet state:

ClipAnkleFlat feet posedLifted feet posed
ui_closeup_shot, ui_fullbody_shot6 cm; the foot turns 21° while the toes turn 5°heel 5 cm through the floor, toes bent up by about 16°flat on the floor
UI_full_shot (inventory)11.7 cm, as in the A poseflat on the floorheel raised, as inside a shoe
ui_gender_selection6.5 cmheel and toes below the floortoes below the floor

[offline: each clip sampled at t = 0, both meshes skinned]

This agrees with the puppets:

  • The creator puppet's appearance, character_creation, is a body group that lists lifted_feet resource, so the creator's idles pose lifted feet flat.
  • The inventory and gameplay use flat_feet when no footwear is worn [wiki: ArchiveXL's {feet} table], and the inventory idle keeps the foot where the flat mesh was modelled.

What the Studio does [source: the Studio]:

  • While a creator idle plays, the character request says puppet: "creator" (xfs/character-request-8), and the host draws V's bare feet as that group lists them (bodyStateFor).
  • With the inventory idle, or Still (the bind pose, where the flat mesh stands flat), the feet follow the footwear.
  • Footwear always lifts them.

When a mod masks the lifted feet out. The lifted-feet choice follows the skin tone (link skin color): each skin tone has its own definition in l0_000_base__full.app, which draws the body component's chunks 5–7 (the lower shins and feet) through a parts override on t0_000_pwa_base__full.ent [resource: vanilla 2.31, basegame_1_engine and basegame_4_appearance]. On the reference profile two mods ship their own copy of that .app (a UV texture framework, which wins, and a toggleable-feet mod) in which the first skin tone's plain definition (l0_000_pwa_base__01_ca_pale) sets that override's chunk mask to 0, while its tone variants (…_00_warm_ivory and the rest) keep vanilla's mask [resource: read natively from each archive]. A parts override's chunk mask only hides (ArchiveXL ANDs it; the Modding Docs' .app page: "You can't un-hide something via partsOverrides") source wiki, and the garment feet components those copies add hide their own chunks unless a worn item's tag rule shows them. So a barefoot V on the first skin tone, which is the creator's default choice, stands without lower shins in the creator on that profile [source + resource; runtime untested: test ask 5]. The Studio draws the same, and the Character panel says so: "Feet not shown: your installed game files hide this part for this choice, as the game would."

Before this: a barefoot V wore flat feet under the close-up clip, so her heels sank through the floor and her toes bent: the crushed feet of the report [offline].

5. The nails' skin

A nails morph mod ships a0_000_pwa_base__nails_l.morphtarget and _r, each with an embedded base mesh of 1,120 vertices, and each vertex is skinned wholly to one fingertip joint (LeftHandThumb2, LeftHandIndex3, …Middle3, …Ring3, …Pinky3) [resource: the base blob's PS_SkinIndices/PS_SkinWeights stream]. WolvenKit 9.0.1 writes that skin when it exports the morph target with the game folder, and leaves it out without it [offline]. The Studio's first export runs without the game folder (PIPE-106), which is why the nails came out rigid and were bound whole to one bone at their centre (rigid-body-part), floating beside the fingers. A morph target whose GLB has no skin is now exported again from its own raw copy with the game folder (MorphTargetSkin), so the nails follow the fingertips as in the game [source: the Studio]. This answers body rendering open question 5: the morph-target export, not the mod.

Open questions

  1. What does each helper joint look like in game in strong poses (raised arms, kneeling, sitting)? The graph's semantics are checked at the A pose only; the twist sign and the bounce slopes act away from it (capture ask 2).
  2. Does the gender-selection screen's puppet use lifted feet, and does it stand on a raised floor (its clip puts even the lifted toes 4 cm under the Studio's floor)?
  3. Does the game evaluate SimpleBounce smoothing towards the static value the Studio uses, and is the breasts simulation at rest identical to the deformation rig's output?
  4. Which face clip plays with the nails and gender-selection loops? The face set has neither, and the Studio keeps the close-up's face.

In-game test asks

Batch with the next session. Record the game version, ArchiveXL, and the body, UV and nails mods in use.

  1. Inventory idle against the Studio. Inventory, V without clothing (underwear on), the default camera: capture three phases a few seconds apart. Compare with the Studio's whole-body view with the Body source on Inventory: stance, arm spread, gaze direction, flat feet.
  2. Helper joints. Photo mode, arms raised and a kneeling pose: close-ups of a shoulder, the torso's side under the ribs and a knee, against the Studio (when poses can play there).
  3. Creator feet. Creator, body page, full-body view, barefoot: the feet must stand flat (the lifted mesh under the creator's clip), as the Studio's Creator full body entry shows them.
  4. Nails. Creator nails section with long nails: each nail must sit on its fingertip through the hand loop.
  5. Feet on the first skin tone (profile with the UV texture framework). Creator, body page, full-body view, barefoot: choose the first skin tone, then the second. Expected from the files: lower shins and feet missing on the first, present on the second. If the first shows feet in game, the engine keeps a cooked component's own chunk mask against a part-scoped override (the .app's cooked components keep vanilla's mask), and the resolver's rule R7 must apply part-scoped overrides only to the part's own components.

Body rendering · Photo-mode poses · Facial expressions · Worn clothing · Pose library design · CC idle · Deformation rig evaluator evidence

Corrections and credit

This page is generated from knowledge/body-animation.md in the repository. If something here is wrong or missing, please open an issue or suggest an edit; links to sources, captures or tests help most.

It builds on the Cyberpunk 2077 modding community’s work. The inline citations link to the wiki pages, mods, tools and repositories each finding came from, and the credits acknowledge everyone we learned from. Citing a source does not mean its authors endorse these notes.