Now I'm starting to really understand why we use sit targets on objects that need to be manipulated...

For your little problem, yes, a small platform is the only way to go. Here is how I see the thing:
float DesiredAngle; // A global variable
DesiredAngle = whatever + PI; // We'll work in radians
if (DesiredAngle > TWO_PI) { DesiredAngle -= TWO_PI; }
The angle I'm talking about is the Z rotation. I add PI (180 degrees) in order not to deal with the zero/2*PI barrier. And that 'whatever' can be (degrees * RAD_TO_DEG).
Use the collision() event.
When llDetectedVel(0) == 0.0, the avatar is not moving on top of the platform any more.
Store the avatar's key in a global variable.
Avatar = llDetectedKey(0);
Jump to another state without collision() event, but a collision_end() to detect when the avatar is leaving.
Turn to physical.
state_entry()
{
llSetBuoyancy(1.0); // To prevent any fall.
llSetStatus(STATUS_PHYSICS, TRUE);
llSetTimerEvent(0.25); // A relatively fast timer
}
timer()
{
float avatar_rot = llList2Rot(llGetObjectDetails(Avatar, [OBJECT_ROT]), 0);
vector euler = llRot2Euler(avatar_rot);
float avatar_angle = euler.z + PI;
if (avatar_angle > TWO_PI) { avatar_angle -= TWO_PI; }
else if (avatar_angle < 0.0) { avatar_angle += TWO_PI; } // That shouldn't happen.
if (avatar_angle < (DesiredAngle - 0.01))
{
llApplyRotationalImpulse(<0.0, 0.0, more>, FALSE)
}
else if (avatar_angle > (DesiredAngle + 0.01))
{
llApplyRotationalImpulse(<0.0, 0.0, less>, FALSE)
}
else
{
llSetTimerEvent(0.0); // We reached the desired rotation, with a little margin
llSetStatus(STATUS_PHYSICS, FALSE);
}
}
collision_end(integer total)
{
state default; // Avatar just left.
}
I know that llApplyRotationalImpulse() is what we need but, honestly, I've never used it... so it's your job to find what 'more' and 'less' are. I used a vector <0, 0, something> because that makes sense for a rotation around the Z axis.
Just my L$ 0.5 of the day...