Characters & assets

Animations

Activities people perform, sex-animation tags, and the rig contract for animation files.

Activities: .ulactivity

What Actor:animate("smoke") plays is an activity, defined in Animations/*.ulactivity (JSON). An activity names an animation, and optionally props held in a hand or the piece of furniture it is done on.

json{
  "write": {
    "anim": "/Game/Heavy/New3/Anims/Daily/write_{G}",
    "props": [
      {
        "mesh": "/Game/Heavy/New3/Anims/Daily/Props/write_prop_1",
        "socket": "rHand",
        "transform": { "location": [-4, 9.4, 4.7], "rotation": [0, 0, 0], "scale": [1, 1, 1] }
      }
    ]
  },
  "na_sleep": {
    "anim": "/Game/Heavy/New2/Daily/fnt_bed01-{g}-0-lie",
    "furniture": "/Game/Heavy/New2/Furniture/fnt_bed01",
    "use": ["bed_bed", "mattress", "futon", "bunk"],
    "use_exclude": ["bdsm", "gurney", "bedside"]
  },
  "sprint": { "alias": "jog" }
}
Field Meaning
anim The animation: a game asset (/Game/...) or a file of your mod, relative to its Animations/ folder (Dance/salsa_{G}.fbx). {G} becomes F or M, {g} becomes f or m, by the person's body
props Meshes attached to a bone (socket), with an offset: a game asset or a .glb/.fbx relative to Animations/ (Props/guitar.glb, or Props/kit.glb#Guitar for one object of a file), or a generated piece under a mod's Rooms/ folder (Rooms/Sets/GenCore/Pieces/GenCore/Product_Mug_Diner.glb)
furniture The piece the animation was made on (a bed, a treadmill): a mesh of your mod (.glb/.fbx, as for props), or the name of an original-game piece (/Game/Heavy/New2/Furniture/fnt_bed01) whose box is read from fnt_bounds.json (below). With use it only lines the animation up on the room's own piece; without use your mod's mesh is spawned under the person, wherever they stand (the original pieces are not in the game and are never spawned)
use Words looked for in the names of the room's pieces (lower case, like fnt_uses.json below). The person walks to the nearest free match and the animation is played on it. An action whose ANIMATION has use is not offered in a room with no matching piece
use_exclude Words that rule a piece out ("urinal" for toilet)
alias Play another activity instead

Another mod's .ulactivity adds ids or replaces them by id. The base game defines 54: smoke, dance, groupdance, drinkbottle, drinkglass, drinktea, eat, call, browse, camera, write, makeup, sweep, weights, yoga, stretch, na_running, jog, swim, sunbathe, pray, meditate, sofawatch, theatrewatch, socialize, takeorder, sell, bbq, work, walk, and more (see ul_Base/Animations/activities.ulactivity).

Outdoors, use an activity without a piece. An action whose activity has use is refused wherever the room has no matching piece, and a park or a beach has no treadmill, yoga mat or pew. When the action can happen anywhere, point it at an activity with neither furniture nor use: the base game has jog (the treadmill clip, running in place), stretch (the yoga clip without the mat) and meditate (the prayer clip without the pew) beside na_running, yoga and pray, which keep their piece. An action offered both in a gym and in a park is two actions, one per kind of place (do_cardio and go_for_a_run).

A mod's own activity:

json{
  "ul_salsa": {
    "anim": "Dance/salsa_{G}.fbx",
    "props": [ { "mesh": "Props/rose.glb", "socket": "rHand" } ]
  }
}

with Animations/Dance/salsa_F.fbx, salsa_M.fbx and Animations/Props/rose.glb in the mod. A later mod shipping the same relative path replaces the file.

Room life: ambient.json

People in a venue are never left frozen. When a room fills, each person either takes a free seat, lies on a free bed, plays a "daily", or stands in their idle. Then every few seconds one free person makes a move: walking somewhere else, going over to talk to someone, sitting down, or changing activity.

  • The dailies are the ANIMATION of the actions available at that venue. A mod's new action with an ANIMATION therefore shows up as background life where the action is offered. Activities that bring their own furniture are never used this way.
  • Seats are any piece whose mesh or node name says it is a seat (chair, stool, sofa, bench…), plus the FntSitChair slots assemblies declare. Beds are the FntSexBed slots.
  • The activities used are sit, lie, socialize, idle_m and idle_f from activities.ulactivity, which any mod can replace.
  • Someone sitting sometimes plays a "seated daily" (call, browse) on the upper body only: the legs keep the seat pose, and the daily's clip and props drive the spine from seated_split_bone up.
  • Some strangers work at the venue, as in the original game: the first one always, each later one 30% of the time. Staff never sit or chat. They write (write), walk over to a customer and take an order (takeorder, food venues only), or serve (sell). Named characters are never made staff.

Animations/ambient.json tunes all of this. A later mod's file overrides the fields it sets:

json{
 "daily_chance": 0.7,
 "sit_chance": 0.5,
 "lie_chance": 0.0,
 "step_seconds": [3, 7],
 "moves": { "walk": 3, "talk": 2, "sit": 2, "daily": 3, "idle": 1 },
 "exclude": ["knockedout", "gun", "strip"],
 "venues": { "hospital": { "lie_chance": 0.4 }, "nightclub": { "daily_chance": 0.9 } },
 "seated_daily_chance": 0.4,
 "seated_dailies": ["call", "browse"],
 "seated_split_bone": "abdomenUpper",
 "staff": {
  "first_chance": 1.1, "chance": 0.3, "age": [18, 64], "first_age": [20, 50], "none_at": ["home", "residential", "toilets"],
  "write_chance": 0.33, "order_chance": 0.5, "order_distance": 250,
  "order_venues": ["restaurant", "bar", "cafe"],
  "write": "write", "order": "takeorder", "sell": "sell"
 }
}

Nobody in a room plays an animation someone else there is already playing, as long as another one is free: seat clips, standing idles, lean loops and dailies are all drawn from what is left. The more clips a mod adds to these folders, the more varied a crowded room looks.

Field Meaning
daily_chance Share of people who play a daily rather than idle (0.7, as in the original game)
sit_chance / lie_chance Share who take a free seat / bed on entry
step_seconds Time between two moves, min and max
moves Weights of each move when a person acts
exclude Activity ids never played as background life
venues Per venue tag: sit_chance, lie_chance and daily_chance for that venue only
seated_daily_chance / seated_dailies Share of seated people who play one of these activities on the upper body (0.4, call/browse, as in the original game)
sit_poses Which seat clips may be used: folder, match ({g} = m/f), reference (the clip seat directions are tuned for) and tolerance (cm). Each sitter gets a random clip whose pelvis height suits the seat, from every clip that faces the same way as the reference . extra: more folders of seat clips, each { folder, match, reference, seat } where seat is the seat height in cm its own reference clip was made for; the base game adds the IdleAnimPack's seated loops (sitting, on the phone, dozing) this way
stand_idles folder, match and chance: standing loops played instead of the legacy idle_m/idle_f that often (base game: the IdleAnimPack's upright idles, 0.6)
lean folder, match, wall_gap (cm from the wall to the person's root), yaw (the clips' facing against the actor's, 180 for the IdleAnimPack) and chance (share of standing people who lean on entry). The lean move in moves sends someone to lean too. The wall is the room's real wall, never a doorway; a spot with furniture in front of it is skipped. Base game: the IdleAnimPack's AS_StandLean_* loops
seated_split_bone The bone from which the seated daily takes over (G8 abdomenUpper)
staff.first_chance / staff.chance Chance that the first stranger / each later stranger works here (1.1 and 0.3, as in the original game; above 1 means always)
staff.age [min, max]: only strangers of these ages work (default 18-64, so no minors and few pensioners serve)
staff.first_age [min, max]: when the first stranger's age is outside staff.age and first_chance is 1 or more, they are given an age in this range, so a venue always has someone working (default 20-50)
staff.none_at Venue tags with no staff
staff.write_chance, order_chance, order_distance, order_venues What staff do: write a third of the time; at an order venue, half the rest walk to a customer, stop order_distance cm away and take an order; otherwise serve
staff.write / order / sell The activity ids played for each

Furniture clips: fnt_uses.json

The original game shipped animations made for one piece of furniture each: looking at a shop shelf, using a fridge, running on a treadmill, a cashier at the till. Animations/fnt_uses.json says which of our pieces each one is played on:

json{ "uses": [
 { "fnt": "fnt_new067", "kind": "use", "match": ["fridge", "refrigerator"], "exclude": ["mini_fridge"] },
 { "fnt": "fnt_new039", "kind": "work", "match": ["cash_register"], "staff": true }
] }
Field Meaning
fnt / kind The clip `Content/Heavy/New2/Daily/-<m
match / exclude Words looked for in the piece's names (actor, mesh and the pack's node name, lower case)
staff Played only by the people who work at the venue

The original furniture <fnt> is the template: it is turned so the person's side faces our piece's free side, and moved so the two front faces meet. People walk over to a matching piece now and then ("use" in moves). A later mod's entry with the same fnt and kind replaces the earlier one.

The original pieces themselves are not in the game; only their size is. Animations/fnt_bounds.json gives each one's box in its own space, in cm: { "fnt_bed01": [minX, minY, minZ, maxX, maxY, maxZ] }. A mod that adds a clip for a piece not listed there adds its box to its own fnt_bounds.json; a later mod's entry for the same name replaces the earlier one.

Sex-animation tags: .ulanimtags

Sex animations are chosen by tags. .ulanimtags files (anywhere in a mod) tag the animations by base name, one per line:

textDDF_DazCow=Anal|Cowgirl|Doggy|Penetration|Vaginal

An animation for N participants is N role clips (Name_0, Name_1…), one per person. Each role is given to a participant by its role letter: M (has a penis), F (does not), S (a woman's body with a penis), or a creature's symbol from its .ulcreature (D dog, H horse, T tentacles). The letter comes from the skeleton the role's clip was made for, else from the name's prefix (DFF_DazBJ is dog, woman, woman). A role with no letter can go to any person, never to a creature, so a scene with a creature only plays animations that name its letter.

Which skeleton stands for which letter is declared in an .ulanimtags file too:

text@skeleton Dog8_Skeleton=D
@skeleton horse2_Skeleton=H

When the skeleton cannot tell the roles apart (three clips made for the same female skeleton), spell the whole clip out, one letter per role in order:

text@roles tff_01=SFF

A position's name in the pose browser and in a partner's proposal comes from an @name line (else the id is shown):

text@name MF_ms07=Missionary Anal

ul_Base/Anims/SexAnimNames.ulanimtags holds the legacy names.

@addtags <id>=<Tag>|<Tag> adds tags to a position wherever it is defined (a .ulanimtags base name, or the motion of a motions.ulanimcat pair), so a mod can tag positions it did not make:

@addtags Missionary100_03_Inserted_loop_G8F_94ac95=CanBeUnconscious

CanBeUnconscious marks a position a body can stay in while passed out (on the back, on the front, on the side, being licked). When someone passes out elsewhere, the scene moves to the marked position sharing most tags with the current one, and while they are out only marked positions can be picked. ul_Base/Anims/SexAnimUnconscious.ulanimtags holds the base list.

Where a position goes in the room comes from an @place line: @place <id>=<Kind> [<cm>] [role<N>] [wall].

text@place MF_CowgirlFast=Ground 50
@place MF_DeskJob=Raised 70 role0
@place MF_LapKiss=Seat 55 role1
@place MF_WallLift=Stand wall
Kind Meaning <cm>
Ground Lying, kneeling, on all fours. Goes on the biggest bed, else on the floor. Height of the ground the clip was made on (0 = floor; Daz couples are often made on a ~50 cm bed; negative = sunk into it).
Stand Someone is on their feet. Floor only, never a bed. Same as Ground.
Seat Role N sits on something (chair, sofa, bed's side); the partner stays on the floor. Seat height in the clip. A seat whose top is within 15 cm is chosen.
Raised Role N lies or leans on a surface (desk, table, bed's edge); the partner stands on the floor. Surface height in the clip, matched the same way.

wall puts the floor position against a free wall, the leaning role's back to it. A desk or a counter counts as a seat for clips made at 65 cm or more (perched on its edge).

Every place offers every height: Rooms/SceneHeights.json lists the heights positions are made on and the pieces that give each one. When a scene starts in a room with nothing within 15 cm of one of them, the first listed piece that fits is added at a free spot, facing the room, and stays there. Only when no listed piece fits do Seat and Raised fall back to the floor.

json{ "heights": [
  { "kind": "Seat", "top": 75, "pieces": [ { "set": "GenCore", "assembly": "G_Seating_Stool_Bar_Chrome" } ] },
  { "kind": "Raised", "top": 70, "pieces": [ { "set": "GenCore", "assembly": "G_Desk_Desk_Office", "top": 74 } ] }
] }

kind is Seat (sat on) or Raised (lain on: at least 100 × 50 cm). top is the height served. A piece's own height is its assembly's surface in assemblies.ulset (the top of its seat, mattress or table top, in cm), or the top given on the piece here. set/assembly are as in the set's assemblies.ulset. Every placed piece with a surface is measured by it, so give one to your furniture: without it a seat counts as 45 cm and a bed as 55. A later mod adds pieces to a height it repeats, or new heights.

A position with no @place is placed as Ground 0. The official places are in ul_Motions/Animations/Motions/SexAnimPlaces.ulanimtags and ul_Base/Anims/SexAnimPlaces.ulanimtags; a mod's line for the same id replaces them.

@roles wins over the skeleton and the name. A person with a penis fills M as well as S, so a trans woman can also take a man's role; S is only ever filled by a woman's body.

Tags are what Game:Filter, Game:AnimatePair and the narration banks' Tag(...) test. The most used ones: Penetration, Vaginal, Oral, Anal, Foreplay, Blowjob, Toys, Finish, Cowgirl, ReverseCowgirl, Missionary, Doggy, ProneBone, Standing, Kissing, Fingering, Handjob, Cunnilingus, Rimming, Deepthroat, Solo, NonSexual, Furniture, Aggressive, AggressiveFM.

A mod's own sex animations

A line whose name matches no game animation looks for the role clips as files instead: Name_0.fbx, Name_1.fbx… under the Animations/ folder of any enabled mod. The name may carry a subfolder.

text# Animations/Sex/MF_LotusSit_0.fbx (the man), Animations/Sex/MF_LotusSit_1.fbx (the woman)
Sex/MF_LotusSit=Penetration|Vaginal|Sitting
@roles MF_LotusSit=MF

The animation's id is the file name without its role number (MF_LotusSit), what Game:playSexAnimation takes. The roles come from @roles, else from the name's prefix when it spells one letter per role (MF_). Each clip keeps its root motion: it places its role relative to the others, so export every role of a position from the same scene, with the figures where they belong.

Props held during an animation: .ultoys

A dildo in a hand, a vibrator, a camera: .ultoys files (JSON, anywhere in a mod, // comment lines allowed) attach a static mesh to a bone of one role for as long as the animation plays. Keyed by animation id:

json{
 "MF_dildo01": [
  {
   "participant": 1,
   "mesh": "/Game/Heavy/New3/Anims/Toys/dildo.dildo",
   "bone": "rHand",
   "location": [-4.73, 6.94, 1.55],
   "rotation": [-0.31, 0.72, 0.145, 0.604],
   "scale": [0.5, 0.5, 0.5]
  }
 ]
}
  • participant: the role index (0 plays _0).
  • bone: the bone of that body the prop follows.
  • location, rotation (a quaternion, x y z w) and scale: relative to the bone.

The prop keeps its own size when the body has a height: only its offset follows the hand. A later mod replaces an animation's whole list, and an empty list removes the props. ul_Base/Anims/SexToys.ultoys holds the legacy game's 15 entries, values unchanged.

Pace, pleasure, resistance and pain: EnergyOrgasm.json, LikesTags.json

Both live in a mod's Anims/ folder; a later mod's file wins.

  • EnergyOrgasm.json — rows of { "tags": [...], "energy": [p0, p1], "orgasm": [p0, p1] }, plus step_seconds (default 0.6: how often a sex scene updates energy and pleasure; the player's Sex scenes: pace setting divides it) and lead_every_seconds (default 12: how often a partner considers leading a new pose). Every step the first row whose tags overlap the playing animation sets how fast each of the two participants tires and how much the pace and their liking turn into pleasure (0.5 for other group sizes).
  • EnergyOrgasm.json resistance — how much each person fights the scene, 0 to 100, shown as a bar on their card. In a non-consensual scene (Game:Filter("Aggressive") or "AggressiveFM", then Sex(aggressor, victim)) everyone after the first actor starts at 100 and stays there; every climax takes climax_drop (3) off, so it never falls much. A willing partner starts at 0 and gains pace_rise per unit of pace error beyond pace_tolerance (too slow counts only slow_factor of it) plus pose_rise per point of pose like under dislike_at, and loses calm per step when pace and pose both suit them. If it reaches 100 they no longer consent: from then on the scene is non-consensual (forced faces and talk, the other one leads, the pregnancy record and witnesses count it as rape). The player's own resistance only moves when they are forced. At 100 resistance, arousal grows arousal_damp (30 %) slower.
  • EnergyOrgasm.json pain — 0 to 100, always starts at 0. It rises only while resistance is above resistance_at (60), by rise × how far resistance is past it × the pace error beyond pace_tolerance (too slow: slow_factor), and eases off by calm per step otherwise. Neither value moves during the dressed wait (IdleSex). At 100 the person passes out: the scene goes on, but they stop gaining arousal, talking and leading, and wear the unconscious face from Climax.json. The scene moves to a CanBeUnconscious position (see @addtags above). Out cold, pain drops by unconscious_calm (1) per step while nobody goes faster than pace_tolerance allows (slowing down works); at 0 they come to. Climax.json faces.pain is the face shown while pain is high.
  • Climax.json options[].motion — what the body does at that ending, in any position. "press": at the next beat (the moment the hips meet) the hips hold, deepest in, for press_seconds (3), with a spasm every 0.75 s. "pullout": the hips draw back pull_cm (14) along the line between the two hips over 0.6 s, the option's fluid only flies once out, and they come back after 3.5 s. No motion: the clip plays on and the fluid flies at once. ul_Base gives Inside and In the condom press, Pull out pullout.
  • SceneTransitions.json — how a change of position looks. Nothing is cut: every body glides from the pose it is leaving into the new clip, for any pair of positions, with no transition clips. Hands, feet and knees resting on the bed, the floor or the partner stay put (IK) while the body moves; the ones whose support changes step over one at a time (lift, carry, plant), and the glide lasts longer for each step. Joints stay within JointLimits.json. blend_seconds (0.9): the glide when the couple stays where it is. When the new position is placed a little further away (up to glide_max_cm, 100), the glide also takes the time to walk there at walk_cm_per_second (120). Further than that (from the bed to a wall), from one kind of @place to another (lying to standing), or when a body rolls over (on her back to on all fours), there is no glide: it is a cut behind a dip to black of fade_seconds (0.35). A later mod replaces the fields it names.
  • SceneSounds.json — the scene's sounds (the cues themselves are in an .ulaudio file, ul_Base ships Audio/Sex.ulaudio). female_voices / male_voices: the voices a person can get, each a Sex.Moan.<voice>.<level> cue family; a person keeps the same one from scene to scene. levels: { "below": 35, "level": "mild" } entries, lowest first: the arousal (0-100) under which each level plays. moan_every_seconds: [2.5, 5], the gap between moans at low arousal, about halved near climax. thrust (Sex.Thrust) plays at each beat in a position with one of thrust_tags (Penetration); thrust_by_tag replaces it for a tag, first match in file order (Deepthroat before Blowjob). mouth_busy_tags: the second role does not moan. min_stroke_cm (1.5): a smaller sway is not a beat. A later mod replaces the fields it names.
  • The scene's Softer / Harder (scene arc) scale every move of the position about its own mean pose, for any clip: no slower or rougher copy of a clip is needed. Speed is the separate Slower / Faster.
  • SceneFluids.json — what someone can let go of during a scene, listed in the scene arc under Fluids: acts of { "id": "pee", "label": "Pee", "fluid": "Urine", "area": "Belly", "strength": 1.0, "npc_chance": 0.0 }. fluid is a type from a fluids mod's Fluids/fluids.json (nothing happens without one); a penis aims at area on the partner. npc_chance is the chance per scene step that an NPC does it unasked (0 in ul_Base). A later mod's entry with the same id replaces it. Its inside rules cover letting go while in the partner: { "tags": ["Vaginal", "Anal"], "part": "Vagina", "area": "Crotch", "leak_fluid": "UrineLeak", "delay": 2.0, "strength": 1.0, "sound": "" }. When the position has one of tags, nothing flies: after delay seconds it runs back out of the partner's part (Vagina or Mouth) as leak_fluid, area is marked wet, and sound (an audio cue, e.g. Sex.Gag) plays. The position does not change. "steer": true lets the player aim the stream with the mouse while it flows (the point under the cursor, on her); how far it reaches depends on the position, since the body only raises and turns the penis so much. "hit_sounds": { "Face": "Sex.Gag" } plays a cue when the stream lands on that area of the partner from outside, at most every 2.5 s.
  • Lua: Actor:getSexResistance(), Actor:getSexPain(), Actor:isUnconscious(). Line conditions in the sex text banks can read A1:resistance and A1:pain like A1:pace and A1:like.
  • LikesTags.json — likes: which animation tags each likes_ stat covers ("likes_doggy": ["Doggy", "ProneBone"]), and bondageSlots: the clothing slots that count as bondage for likes_bondage. A partner likes an animation by the mean of the matching stats.

Restraints that hold the body: .ulrestraint

<mod>/Bodies/*.ulrestraint (JSON) makes worn restraints pose the body over whatever animation plays, as the legacy game did with handcuffs and leg cuffs. While anything is worn in one of slots, every bone from bones down takes its rotation from pose (looped):

json{
 "restraints": [
  { "name": "arms", "slots": ["Handcuffs"], "bones": ["lCollar", "rCollar"],
    "pose": "/Game/Heavy/New2/Anims2/MF_layBJ10_1.MF_layBJ10_1" }
 ]
}

A later mod replaces an entry with the same name. Only rotations are taken, so the body keeps its own proportions and height.

In a sex scene, a gag (Gag slot) rules out Blowjob animations and a chastity belt (Chastity) rules out Vaginal and Anal ones. Items whose id starts with Bdg_ are offered as restraints in the scene's Restraints panel.

The rig contract

Every animation in Underlives plays on the Genesis 8 skeleton and is matched by bone name, not retargeted. Animation files from mods (activities and sex animations) follow this contract:

  • Exported from Daz Studio as FBX, on a Genesis 8 Female or Male figure.
  • Bone names unchanged (hip, pelvis, lThighBend, rHand…).
  • One animation per file; the first take is used.

A clip keeps the proportions of whoever plays it: bones take their rotation from the file and their length from the body playing the clip, so an animation exported on the female figure plays cleanly on a man and the reverse. Only the hips' movement (a bob, a thrust) is carried over, from the player's own hip height. Morphs in the file are ignored.

Preparing an export

Daz cannot export an animation without its figure. Tools/AnimStrip/strip_fbx_mesh.py (in the game repository) removes the mesh, skin, morphs and materials, collapses the curves of bones that never move, and leaves the motion byte for byte; a 48 MB export typically becomes 2 MB.

textpython Tools/AnimStrip/strip_fbx_mesh.py MyIdle.fbx               # in place, MyIdle.fbx.bak kept
python Tools/AnimStrip/strip_fbx_mesh.py MyPose.fbx --order MF    # two people: MyPose_0.fbx (man), MyPose_1.fbx

Daz writes the bones' rest position in one of two ways depending on the export options (in each bone's translation, or apart as a rotation offset with the translation holding only the motion); both are read correctly, so either export works.

A Daz scene with several people exported as one file is split into one file per figure, in the Name_0, Name_1 order a sex animation's roles use, each keeping its place relative to the others.

Previewing

The animation test level starts on a position, both bodies undressed:

textUnrealEditor.exe "<project>.uproject" "/Game/Maps/RoomTest_Generic?game=/Script/Underlives.SexAnimTestGameMode" -game -SexAnim=MF_MyPose -windowed

-SexAnimShot takes a screenshot to Saved/SexAnimTest/ and quits; -SexAnimHeight=0.6,0 gives the participants a height; -SexAnimView=yaw,pitch,distance and -SexAnimAimHips place the camera on the couple.

After re-exporting, replace the files in the mod and click Reload files: no restart. If a position does not play, the log says why (a role file that does not load, or participants that do not fit its role letters).

Fixing a clip: .ulanim over .fbx

A .ulanim file with the same path and name as an .fbx plays instead of it: Motions/couple.sex/MyPose_2M.ulanim replaces Motions/couple.sex/MyPose_2M.fbx, wherever the FBX is used (a position's role, an activity's anim). The FBX is not touched; delete the .ulanim to bring it back. A mod can ship a .ulanim under the path of another mod's clip to correct it.

The posing editor writes these files. In its Animations tab, the IN THE GAME list holds every motion of the game's catalogs (motions.ulanimcat). A click opens every role of the motion on a character of the right sex, in their places. Fix it with keys and IK, then To Game writes one .ulanim per role beside its FBX. Limits in the viewport bar shows (and, when on, saves) the joint limits a scene puts on the clip. The Motion Browser's In the game button does the same for a clip shown from its in-game rows, after moving a body by hand. Both work in a packaged game.

A .ulanim is the posing editor's text format: keyed bone tracks at 30 frames per second. Only the hip moves; every other bone only turns, with its length taken from the body playing it. A couple's places are carried by each role's hip, relative to one shared spot.

Joint limits

In a scene, every clip is held inside the joint ranges of ul_Base/Anims/JointLimits.json (degrees from the rest pose: twist about the bone, swing_a and swing_b across it, and a hinge axis for knees, elbows and fingers). clip_margin_deg gives the clip's own pose that much extra room; what IK adds (a hand on a partner, a foot held through a change of position) never gets it. The ...Twist bones of the arms and thighs only turn about themselves. A mod's file replaces the joints it names.

Body height in sex animations

Clips are authored for two bodies of standard Genesis 8 proportions. When a participant has a height, the game keeps each pelvis where the clip puts it, so the two still meet, and pulls hands and feet by IK onto the spot of the partner's body they were touching in the clip (a hand on a hip follows that hip down a short body). A foot planted on the floor or bed keeps its place. Nothing changes when nobody in the scene has a height. A very short person in a standing position may end up on tiptoe: the pelvis wins.

Clothing and hair follow the same skeleton: see Clothing.