The store width follows the destination, not the source
Read a wide global, run some arithmetic, then write into a narrower global, and the two memory opcodes end up coming from different types. The load is lwz, because the source happens to be an int. The store is stb (or sth), because the destination is a byte (or halfword). Nothing in between narrows the value for you. A stb already keeps only the low 8 bits, so the upper part of the computed word just drops on the floor.
It's the same narrow-store rule from the type lesson, stb/sth and all, except now it follows arithmetic rather than a plain load. The store opcode hands you the destination's width, and the load opcode hands you the source's.
Here's stepPhase(). It reads the int global gTicks, multiplies by a constant, and drops the low byte into the u8 global gPhase:
lwz r0, gTicks@sda21(r13) # read int global gTicks
mulli r0, r0, 5 # gTicks * 5 (constant multiply, not a power of two)
stb r0, gPhase@sda21(r13) # gPhase = (u8) result
blr
mulli is the catch-all constant multiply, and 5 isn't a power of two, so there's no shift to strength-reduce it into. That product fills the whole of r0, and yet stb writes back only the low byte to the u8 global, which is the (u8) cast made flesh. Your target scales a different int global by a different constant ahead of its own narrow store. The mulli immediate and the store opcode are what you read off it.
Your task
The globals are declared for you: gRaw (int) and gLevel (u8). Write clampLevel (no arguments, no return) to reproduce the assembly above.