Pick one of two floats
Clamping a value to a floor — if (x < lo) return lo; return x; — is a two-outcome function, and IDO compiles it with a branch-likely form and a register copy you haven't met on the FPU yet. Here's atLeastW(x, lo):
0: c.lt.s fa0, fa1 # flag = (x < lo)
4: nop
8: bc1fl 0x1c # flag false (x >= lo)? return…
c: mov.s fv0, fa0 # (likely slot) …x itself
10: jr ra
14: mov.s fv0, fa1 # fall-through: return lo
18: mov.s fv0, fa0 # duplicated tail — dead, but counted
1c: jr ra
20: nop
mov.s is the FPU's register copy — or rd, rs, zero's cousin. And the branch is the likely form you know from loop back-edges, doing if-conversion duty: the mov.s in its slot runs only when the branch is taken, so each outcome gets its own copy-into-fv0.
Trace it both ways. Flag false (x >= lo): branch taken, slot runs, fv0 = x, land on the second jr. Flag true (x < lo): slot annulled, fall through to the first jr, whose delay slot sets fv0 = lo. The mov.s at 0x18 is a duplicated tail the optimizer strands there — unreachable, but part of the byte count, so your C must produce it too (writing the natural two-return if does).
This exact skeleton — compare, bc1fl, two mov.s returns, one stray tail — is how the game spells every float min, max, floor, and ceiling against a variable. Which one it is lives entirely in the compare's mnemonic and operand order.
The target caps a value against a ceiling instead. Remember what this instruction set doesn't have.
Your task
Write func_80121f6c to reproduce the target assembly.