I release it under a DWTFYW licence: Do What The Frak You Want (but don't hold me responsible for anything).
The beacons must be named "beacon" (lower case). If you set the description of a beacon to "stop" (lower case), the tramway will stop over it. It's up to you to define when and how it will restart. This allows you to define complex paths in between the stops.
Don't start the tramway too close from a stop beacon. (Hey, this is a skeleton of script!)
And you can't predict in which direction it will turn... (Skeleton, etc, etc)
There must be at least 4 beacons... 6 to be sure. They can be fresh rezzed cubes or you can build the track with them. Just don't put scripts in them.
Tested and debugged in-world. You can come and test my touring bot using a similar technic here:
secondlife://Dunnideer/168/172/6/
You'll see a few creatures using llMoveToTarget() at constant speed... but without beacons. (The area is a bit under work... Wear a hardhat!)

Quote me to retrieve the layout.
------------------------------------------------
list Previous = [ZERO_VECTOR, ZERO_VECTOR, ZERO_VECTOR];
integer Target;
vector TargetPos;
float Hover = 1.0; // How high above the beacons?
float Speed = 3.0; // Don't go too fast or the tramway will misbehave
integer STOP;
default
{
on_rez(integer param) { llResetScript(); }
state_entry()
{
llSetStatus(STATUS_PHYSICS, FALSE);
llSetBuoyancy(1.0);
}
touch_start(integer num)
{
if (llDetectedKey(0) == llGetOwner()) // Click to start
{
llSetStatus(STATUS_PHYSICS, TRUE);
state sensing;
}
}
}
state sensing
{
on_rez(integer param) { llResetScript(); }
state_entry()
{
llSensor("beacon", NULL_KEY, PASSIVE, 10.0, PI); // YMMV, so adapt the radius!
}
sensor(integer total)
{
integer i = 0;
for (; i < total; ++i)
{
vector beacon = llDetectedPos(i);
beacon.z += Hover;
if (llListFindList(Previous, [beacon]) == -1)
{
STOP = ((string)llGetObjectDetails(llDetectedKey(i), [OBJECT_DESC]) == "stop"
;TargetPos = beacon;
state moving;
}
}
}
}
state moving
{
on_rez(integer param) { llResetScript(); }
state_entry()
{
Previous = [llList2Vector(Previous, 1), llList2Vector(Previous, 2), TargetPos];
Target = llTarget(TargetPos, 0.05);
llLookAt(TargetPos, 0.75, 0.5);
llMoveToTarget(TargetPos, llVecDist(llGetPos(), TargetPos) / Speed);
}
not_at_target()
{
float dist = llVecDist(llGetPos(), TargetPos);
if (dist < 0.09)
{
llTargetRemove(Target);
if (STOP)
{
state stopped;
}
else
{
state sensing;
}
}
else //if (! STOP) // Add this test to slow down before to stop
{
llLookAt(TargetPos, 0.75, 0.5);
llMoveToTarget(TargetPos, dist / Speed); // <<< THE line
}
}
at_target(integer num, vector dest, vector pos) // Failsafe. No really useful.
{
llTargetRemove(Target);
if (STOP)
{
state stopped;
}
else
{
state sensing;
}
}
}
state stopped
{
on_rez(integer param) { llResetScript(); }
state_entry()
{
llSetStatus(STATUS_PHYSICS, FALSE);
llOwnerSay("Stopped"
;}
touch_start(integer num)
{
if (llDetectedKey(0) == llGetOwner()) // Click to continue
{
llSetStatus(STATUS_PHYSICS, TRUE);
state sensing;
}
}
}
------------------------------------------------