Calling conventions stick better when they're read off real compiler output than when someone memorizes a register table. A good exercise is a C function with three int parameters that returns their sum, compiled for RV32 once at -O0 and once at -O2.
At -O0 the listing is noisy. The function sets up a frame, stores a0, a1 and a2 to the stack, loads them back, adds, and leaves the result in a0 before ret. At -O2 it collapses to two add instructions and ret. Same convention, very different listing, and the comparison is the lesson. The arguments arrive in the argument registers either way, and the result leaves in a0 either way. Everything else was the compiler's own bookkeeping.
Then make the function call another function and ask why ra now has to be saved. ret is just jalr x0, 0(ra), and the inner call overwrites ra.
Skip the unoptimized step and people tend to think the stack traffic is part of the convention. It isn't. What I'm unsure about is the order. Starting at -O2 is cleaner, but then the frame setup turns up later as a surprise.