What I was/am missing is the formula that says how the next target is calculated.
I tried the wiki but didnt find it
did you take the calculation from the source code?
N = New difficulty (at block 2016N)
O = Old difficulty
T2 = timestamp of block 2016N - 1
T1 = timestamp of block 2016N - 2016
N = O * (2 weeks) / (T2-T1)
Of course, in actuality the calculation is based on target, new target = old * (T2-T1) / (2 weeks) and the difficulty derives from it. I didn't see the code for this but I'm guessing first an integer division by 1209600 (seconds in two weeks) is done and then the multiplication. There's also the [1/4, 4] limit.
There's actually a bug in this calculation, it should have used T1 = timestamp of block 2016N - 2017, and this bug makes possible a form of attack. But at this point it is considered not worth correcting it.
It's supposed to be explained at
https://en.bitcoin.it/wiki/Difficulty#What_network_hash_rate_results_in_a_given_difficulty.3F but maybe it's not very explicit. Also see
http://bitcoin.stackexchange.com/questions/855/what-keeps-the-average-block-time-at-10-minutes.
EDIT: Here's the relevant piece of code (from main.cpp). It appears the bignum used can handle doing the multiplication first and then division:
static const int64 nTargetTimespan = 14 * 24 * 60 * 60; // two weeks
static const int64 nTargetSpacing = 10 * 60;
static const int64 nInterval = nTargetTimespan / nTargetSpacing;
...
// Go back by what we want to be 14 days worth of blocks
const CBlockIndex* pindexFirst = pindexLast;
for (int i = 0; pindexFirst && i < nInterval-1; i++)
pindexFirst = pindexFirst->pprev;
assert(pindexFirst);
// Limit adjustment step
int64 nActualTimespan = pindexLast->GetBlockTime() - pindexFirst->GetBlockTime();
printf(" nActualTimespan = %"PRI64d" before bounds\n", nActualTimespan);
if (nActualTimespan < nTargetTimespan/4)
nActualTimespan = nTargetTimespan/4;
if (nActualTimespan > nTargetTimespan*4)
nActualTimespan = nTargetTimespan*4;
// Retarget
CBigNum bnNew;
bnNew.SetCompact(pindexLast->nBits);
bnNew *= nActualTimespan;
bnNew /= nTargetTimespan;