Hereās the reference implementation!

First define some constants:

```
my $IA = 16807; # constant used for RNG (see p37 of Simulation by Ross)
my $IM = 2147483647; # constant used for RNG (2^31-1)
my $URPING = 1184083200; # ur-ping, ie, the birth of timepie/tagtime!
# $seed is a global variable that is really the state of the RNG
$seed = $initseed = 666;
$gap = 45*60; # 45 minutes between pings on average
```

And some helper functions:

```
# Return a random integer in [1,$IM-1]; changes $seed, ie, RNG state
# (This is ran0 from Numerical Recipes and has a period of ~2 billion)
sub ran0 { return $seed = $IA*$seed % $IM; }
# Return a U(0,1) random number
sub ran01 { return ran0()/$IM; }
# Return random number drawn from an exponential distribution with mean $gap
sub exprand { return -1 * $gap * log(ran01()); }
# Round to nearest integer
sub round1 { my($x) = @_; return int($x + .5 * ($x <=> 0)); }
```

Then define a function prevping that takes the current unixtime and returns the unixtime of the previous ping according to the universal ping schedule. Once you have that you can keep calling nextping() with the timestamp of the last ping to get the time of the next ping.

```
# Take previous ping time, return random next ping time (unixtime)
# NB: this has the side effect of changing the RNG state ($seed)
# and so should only be called once per next ping to calculate,
# after calling prevping.
sub nextping { my($prev)=@_; return max($prev+1,round1($prev+exprand())); }
# Compute the last scheduled ping time before time t
sub prevping {
my($t) = @_;
$seed = $initseed;
# Starting at the beginning of time, walk forward computing next pings
# until the next ping is >= t.
my $nxtping = $URPING;
my $lstping = $nxtping;
my $lstseed = $seed;
while($nxtping < $t) {
$lstping = $nxtping;
$lstseed = $seed;
$nxtping = nextping($nxtping);
}
$seed = $lstseed;
return $lstping;
}
```

##
For posterity, a totally different approach for a different universal ping schedule

Alice Monday (@alice) and I came up with this together but weāve never actually used this.

The advantage of this version is you could throw away all the fancy math and it would make sense to anyone how and why it works. The disadvantage is itās more computationally intensive. Also it requires picking a kind of arbitrary parameter for the granularity ā how far apart pings in principle can be. Iād suggest .125 seconds since thatās a safe lower bound on human reaction time. (I think the only way this matters is if you want unbiased data on how much time you spend answering TagTime pings.)

Also letās call an eighth of a second an *octant* even though thatās sort of an abuse of terminology. We can get the current unixtime measured in octants by multiplying normal unixtime by 8. Or if you get unixtime in milliseconds, multiply it by 8/1000.

On to this alternate algorithm for a different universal ping schedule:

Every single unixtime (in octants) either has a ping or not. The probability of a ping is just `1/(45*60*8)`

ā the denominator being the number of octants in 45 minutes. To make it universal you use the unixtime itself as the seed for the RNG for deciding if that octant has a ping. So, using the `ran0`

function above, unixtime t has a ping if:

```
IA * t % IM / IM < 1/(45*60*8)
```

i.e.,

```
16807 * t % 2147483647 < 99421
```

So you can start computing it at any time without worrying about history at all. Of course you donāt want to be running a loop doing a calculation 8 times per second so you walk forward, checking each unixtime (measured in octants) with the above inequality till you find the next one a ping will happen at (on average that means checking the inequality 21,600 times) and just sleep till then.

On reflection, I think itās inferior to the mathy approach. For one thing, I donāt know if feeding very similar seeds to the RNG spoils it. Seems like it might.