I was reminiscing about my time botting runescape the other day. It made me want to take a trip down memory lane. I was considering making a script, but I quickly remembered the effort involved and decided to not dedicate the time. My favorite part of making scripts was adding randomization to evade bans. After looking through some of my old scripts, I quickly realized how easy it to detect using simple statistics it would be. I did an experiment to show off how easy scripts could be to detect. I decided to focus on alching as it is very simple.
Alching, you note the items and put them in that one inventory slot then you click in the same place over and over again. You know what I'm talking about. I bought 1000 nature runes, and 1000 maple long bows and I alched all of them by hand while I watched a movie. But I recorded all my mouse clicks using this python script.
Code:
from pynput.mouse import Listener
import time
import atexit
file_name = f"click_data_{int(time.time())}.csv"
output_file = open(file_name, mode="w")
output_file.write("time,x,y\n")
def exit_handler():
output_file.close()
print("Stopping. ")
atexit.register(exit_handler)
def on_click(x, y, button, pressed):
if pressed:
t = int(time.time() * 1000)
print(t, x, y)
output_file.write(f"{t},{x},{y}\n")
with Listener(on_click=on_click) as listener:
listener.join()
This creates a .csv file with time in milliseconds, x coordinate, and y coordinate for each click you make. In total it took me 2411 clicks to finish. I did it in one sitting and I didn't take any breaks. Occasionally, I would click to fast and this would select the item I am alching (like when you are using an item on something). This would cause me to reopen the magic tab and start again. This caused extra clicks a few times. I attached the data if you want to look at it.
When I scripted I would space out actions with random intervals of time using functions like randomRange(x,y) and gaussRangeInt(x, y) from SRL-6's math.simba. I did not entirely understand what these methods did at the time, but I do now. It's pulling a random number from a uniform and normal distribution respectively. But these distributions are far from human. They can also be picked up on quickly using statistical tests like this
The basic idea is to compare the interval of time between my clicks with the interval of time between a script's clicks.
Back when I would use bots, I commonly would use @fady's alching script to level up my magic. (I forget how to @mention). I simulated data from this script to compare to mine.
Here is the basic components of his script, I removed most of it, but its enough to get the idea. Apparently I wrote the antiban function 
Code:
procedure antiBan(); // Credit goes to Camel! Slight modification of the antiban in his Aircrafter.
var
i: integer;
begin
i := random(3000);
if i < 70 then
begin
writeLn('Doing antiban');
case i of
1..4: begin
writeLn('Taking Mini-break');
wait(gaussRangeInt(180000,300000));
if not isLoggedIn() then
begin
players[currentPlayer].login();
wait(gaussRangeInt(300,2000));
minimap.setAngle(MM_DIRECTION_NORTH);
mainscreen.setAngle(MS_ANGLE_HIGH);
closePollWindow();
end;
end;
5..13: begin
hoverSkill(SKILL_MAGIC);
wait(gaussRangeInt(1500,5000));
gameTabs.openTab(TAB_BACKPACK);
end;
14..16: mouseMovingObject();
17..29: sleepAndMoveMouse(randomRange(3000,6000));
30..40: begin
mouseOffClient(Random(4));
Wait(gaussRangeInt(10000,15000));
end;
41..48: Wait(gaussRangeInt(2000,3000));
49..70:begin
pickUpMouse();
wait(gaussRangeInt(1500,3000));
end;
end;
writeLn('Antiban Done, Resuming Alching');
inc(a);
initiate();
end;
end;
procedure alch();
begin
tabBackPack.mouseSlot(slot, MOUSE_LEFT);
wait(gaussRangeInt(100, 900)); //Waiting 0.1-0.9 seconds between clicks
typeSend(players[currentPlayer].strings[0], false);
wait(gaussRangeInt(600, 1000)); // Waiting 0.6-1 second to repeat
end;
begin
repeat
antiBan();
alch();
until someCondition();
end
The typeSend uses a keybind that RS3 had to cast the spell I'm going to consider that a click. I recorded my data on OSRS. I don't think this really matters for what I'm trying to do. Here is some python code to generate data for the time between clicks similar to the script.
Code:
def gauss_range_int(min_i, max_i):
mean = (max_i+min_i)/2.0;
dev = abs(max_i-min_i)/ 2.0 * 0.3 #* __gaussRange; # SRL-6 uses 0.3
# didn't include hard bounding, doesn't matter much imo
return round(random.gauss(mean, dev))
# Simulate data for the time between clicks based on fady's script
simulated_td = []
for i in range(1200):
# Antiban
r = random.randrange(0, 3000)
wait_time = 0
if r <= 4:
pass # Remove the minibreak
elif r <= 13:
wait_time += gauss_range_int(1500, 5000)
elif r <= 16:
pass # idk what to do here
elif r <= 29:
wait_time += random.randrange(3000, 6000)
elif r <= 40:
wait_time += gauss_range_int(10000, 15000)
elif r <= 48:
wait_time += gauss_range_int(2000, 3000)
elif r <= 70:
wait_time += gauss_range_int(1500, 3000)
wait_time += gauss_range_int(100, 900)
simulated_td.append(wait_time)
wait_time = gauss_range_int(600, 1000)
simulated_td.append(wait_time)
simulated_data = pd.Series(simulated_td)
This simulation isn't perfect, but its close enough to what the script would have given you. Here is the histograms and plots of human data and the script's data.

X Axis, is the click number. The human data is chronological. Y Axis is the milliseconds between each click.

X Axis is the milliseconds between clicks, Y Axis is the count of observations. This approximates the distribution of the time between clicks.
As you can see, they're quite different. Obivously the simulated data will be different each time as its generated randomly but the distribution will be roughly the same. The scripts distribution of time between clicks is much tighter. While mine is much wider, has a positive skew, and is borderline bimodal. It's certainly not normal or uniform. The antiban function appears to work well. It creates an occasional spike in time between clicks just like we see in my data. But these spikes make it hard to see the data, so I'm going to zoom in and crop them out. Here is the data zoomed in.

The script data has a fairly consistent variance, while my data has a time varying variance. I had periods of time where I was clicking faster/slower, and being more/less consistent with my time between clicks.
I also noticed that my time between clicks has dependency. Here is the ACF (auto correlation function) of the simulated data and my data. I increased the size of the simulated data to 100,000 for this.


The Y axis is the autocorrelation. The X axis is the amount of lag in the series.
My data had much more auto correlation than the scripts. This might just be a quirk of the way I click the mouse but quirks are human. There is quite a bit more stuff to look at. Perhaps I will edit this post with more content in the future there is a few ideas I'd like to try. But currently I'm tired of writing and I am going to go to bed. Hopefully someone finds this interesting.