edit 3: Looking at CvCity.cpp, I think I found what's allowing them to spawn both past the limit and with negative crime. The relevant section of code. iCurrentValue is, if I'm reading this correctly, the crime property value in the city. I'm not familiar with the structure of the rest of the code, but I don't believe there's a check on whether Crime is positive or negative before the adjustment for iNumCriminals.
Code:
{
iCurrentValue -= ((iCurrentValue/10) * iNumCriminals);
}
iCurrentValue = std::max(0,iCurrentValue);
Thus, what is happening:
When there's no criminals in the city, iNumCriminals is 0, iCurrentValue is unchanged by the -=, and the second line then zeroes iCurrentValue if it was negative, or leaves it alone if not. No issues here.
When there are more than 10 criminals present in the city but iCurrentValue is negative, we wind up subtracting a negative value of magnitude greater than that of iCurrentValue from it, thus resulting in a positive value. Example, crime is -1000, 15 criminals in the city. The calculation becomes -1000 - ((-1000/10)*15) = -1000 - (-1500) = +500. Thus, we have a negative crime value being flipped to a positive value and passed to the spawning code, while at the same time bypassing the limit.
I can see several potential fixes for this, not sure what would work best in terms of performance or other issues, I'm not actually a programmer. We could simply use a conditional to bypass the entire block of criminal spawn code when crime is negative to begin with. We could rewrite the reduction-per-criminal calculation to multiply instead of decrement (something along the lines of iCurrentValueAdjusted = iCurrentValue * max(0,((iMaxCriminals - iNumCriminals)/iMaxCriminals)) where iMaxCriminals is however many criminals you want the cap at). We could rewrite it to enforce a maximum effective value on iNumCriminals (iCurrentValue -= ((iCurrentValue/iMaxCriminals) * (min(iNumCriminals,iMaxCriminals))
Note that the above is just pseudocode, not actual code, since I don't know C++. Also, I used iMaxCriminals so that if anything crops up in the future or changes are made, we don't all wind up wondering what this mysterious "10" value is meant to be or where it came from, and to make it easier to tweak.
I'm not sure what was allowing it to overshoot in the positive-crime cases, though.