Erik Skau
- ewskau
- ewskau
- America/New_York
- Yes
- No
Erik Skau: It's worse than that. The random values are approximately normally distributed (sum of three uniform values for speed and jump strength; only two for health), so outliers are very hard to get but just as easy to reduce toward the average.
I've been thinking how you might do this well. One idea I've had is to perform all stat calculations like a random walk, and use a bijection to warp that space to some desirable shape. To put it more concretely:
wildHorse() = wildAverage + normalRandom(wildSpread)
breedHorses(x, y) = (x + y) / 2 + normalRandom(breedingSpread)
logistic(x) = 1 / (1 + exp(-x))
logisticInverse(x) = -log(1 / x - 1)
statValue(x) = minValue + (maxValue - minValue) * logistic(x)
statValueInverse(x) = logisticInverse((x - minValue) / (maxValue - minValue))
statValueExp(x) = exp(log(minValue) + (log(maxValue) - log(minValue)) * logistic(x))
statValueExpInverse(x) = logisticInverse((log(x) - log(minValue)) / (log(maxValue) - log(minValue)))
To get a stat for a wild horse, use statValue(wildHorse()). To breed, use statValue(breedHorses(statValueInverse(x), statValueInverse(y))). You might mix parents in some other way, e.g. statValue(statValueInverse((x + y) / 2) + normalRandom(breedingSpread)) if you want a mix of average and outlier to be less of an outlier. statValue can be changed to adjust the profile, with statValueExp as an example. Lots of room for tweaking.
Note that this example has a restricted domain that can bite you. If you hack in a horse with a stat below minValue or above maxValue, breeding is undefined or at least complex.
If you approach it naturally (which should take at least hundreds of attempts with reasonable parameters, and becomes impossible to do without reading raw values as changes become imperceptibly small) and minValue/maxValue were chosen wisely, not even rounding error can push you over the edge, so only true cheaters are affected. Leave them in their misery with NaN stats or add some protection.
I'm not entirely happy with all properties of this, but honestly it should be pretty good for the purposes of the game. I've also deliberately crafted it so it works with the current stat system. Any better proposals?
P.S. It's impossible to get more than new Random(97128757896197L).nextGaussian() == 7.995084298635286. I analyzed it because I was wondering if it could theoretically produce extreme results as a fluke. Turns out it can't.
Erik Skau: Me too. I wrote my suggestion earlier in the thread too, but I'd prefer using just one of the parents for each stat, adding normally distributed noise (magicNumberStandardDeviation*Random.nextGaussian()) to it and capping it to the limits. It follows the pattern that is used for the colours too, that the foal clearly "looks like" one of its parents. Will the horse be strong like it's father and quick like his mother, or will he be dealt a bad hand in the gene lottery. On average it's the same as using the average, but it's more variable this way. One horse in 64 gets all stats from the better parent and get noise that improves those, and one horse in 64 will be worse than its parents in all aspects.
jonathan2520: Your example is quite hard to follow without diving further into the mathematics. Would this random walk be able to improve a stat that is already good and what would be the odds?
Erik Skau: There is a significant difference, though. Your method has a hard cut-off with min and max. That means you can breed a bit and simply hit the cap to get a 'perfect' horse. That may be a hoarder's dream, but it doesn't seem quite right.
My method is essentially the same but with a gradual cut-off. Instead of just hitting the cap, you'll only get ever closer (although with an exponential closing rate, eventually you'll be pretty much there). It won't make a huge difference for those casually trying to get a better horse, sure. But it really sets those who take breeding seriously apart.
On the other hand, Minecraft is a game of hard edges and rules that are easy to implement using simple binary logic. But then horses are living things which should perhaps set them apart, and they already have a smooth distribution as a precedent. What would really be best?
Anyway, I'm not the one calling the shots. And so far neither is Mojang. Grrr.
Erik Skau: There would still be the random variation / noise, which could either increase or decrease the value. If the parents' average would be used, the noise would have to be in proportion of the difference of the parents if the foals should have a chance to exceed either of the parents (see my comment here). It might work, I'm just wary of using averages since the problem at hand is partly caused by averages. If you have two horses that are very good in some stat and bad in others, getting an all-around good horse is hard. Toy example:
a: 10,0
b: 0,10
c: 5,5 (a+b)
d: 7.5, 5 (a+c)
e: 3.5, 7.5 (b+d)
And so forth.
Erik Skau: Well, the math part is already thought out for them, free of charge
It's not that hard anyway, this picture tells a lot - it just stretches the limited range to an infinite one for adding the noise and then we do the reverse. I like the fact that perfection is unobtainable - there's still competition even at the high end. The normalisation that is done is a bonus - every stat can use the same formula. Otherwise you would have to keep track of the different standard deviations for all the stats.
I too wonder if anyone at Mojang reads these - I can imagine it doesn't help that this bug has been assigned for a long time.
Bonus implementation tips for Mojang: In case you write out the min/max values in the class for the limiter, you can make the stat generation use them instead of the hardcoded different formulas that are used now to make sure they match.
Altti Tammi: I did consider the error function. The roundness is nice. But Java doesn't have it, and its inverse is even harder to get.
x/sqrt(x^2+1) would need some special handling of huge numbers, yes. Its problem is not division by zero but division by infinity (awkwardly producing zero) and eventually division of infinity by infinity (producing NaN). Not hard to fix, though. The most natural expression of its inverse (x/sqrt(1-x^2)) only has a division by zero at the edges, producing a correct infinity.
Erik Skau: The problem without some kind of tapering is that the same increment has the same cost at every level, even if horses at that level should be rare. You may want to tweak the standard deviations and all so the casual breeder can get a reasonable improvement at a reasonable cost (time, materials). Then a hardcore breeder only needs to invest a small constant factor more to get a coveted
perfect horse. Strong horses should be more valuable than that.
I figure someone at Mojang might eventually read this, if they read as much as they write. This shouldn't be too much math to verify. The complexity is much lower than that of much of the rest of the game, which is somehow only moderately buggy.
I just made an account to follow/vote on this issue. I hope it gets resolved soon. (I do not know the ranges of horse stats, so I make up numbers to convey concept.)
If horse breeding in fact works as offspring stat=(Pa+Pb+Random)/3, where the random number is from some set distribution like [1,2] (the distribution Pa and Pb came from), then this does make horse breeding relatively worthless. If Pa and Pb are both very good, then the Random will almost never be as good, and so will most likely make the offspring more average. Breeding in this system will "always" bring things to average.
I propose the relatively easy fix of offspring stat=(Pa+Pb+Random)/2, where the random number is from a distribution like [-1,1] (centered around 0). Notice the divisor is 2. With this method the expected value of an offspring will be the average of the two parents, 1/2 of the time the offspring will be better/worse at a given trait, and 1/8 of the time the offspring will be better/worse at all 3 traits. Also, this should be pretty similar to how selective breeding works in real life.
Hope this gets resolved soon.
jonathan2520 Your method seems like a very elegant mathematical solution (didn't actually run through the algorithm, but I think I got the intuition), but probably not very appealing from a programming perspective, as well as it is not as readable as code. I think I would prefer something simpler such as:
offspringStat = min( max( (Pa+Pb)/2+random, minimumHorseStat), maximumHorseStat); //random comes from some distribution with expected value of 0. Probably Gaussian-ish.
Your idea nicely addresses and fixes the problems that occur with this method when both parent horses are an extreme case. But I think that practically either one would suffice and that very few players would be able to distinguish between the two, especially not until they had very good horses anyway, which is when the differences between the two methods would manifest themselves. I could be wrong about this, I think the easiest way to find out would be just to implement both and test them seeing if there is any real difference from the players perspective.
Altti Tammi I also thought about your method, and I would be quite satisfied with it as well. My only complaint with it would be that it does not necessarily mean good game progression, but this is really just my preference. To demonstrate this let me give an example.
ParentA is good at running/jumping but has crappy health. ParentB has good health but really bad at running/jumping. The offspring could either be perfect, or flawed in one or more aspects.
In the case of perfect, then the player has achieved the ideal horse after 1 breeding instance, not very rewarding in my mind.
In the case of flawed, the horse will be really bad at one or more of those things, in my mind making the horse not fun, and not really a step up from either parent.
If on the other hand things were averaged, the first case of a perfect offspring is not possible, and the second case would be a more well rounded horse rather than a horse extremely flawed in one or more aspects, which I think makes better for game progression.
But again, I like your method too, and would be quite happy if it was implemented.
Any word from anyone on the Mojang team about this? Is this all moot because they are already working on some fix?
Altti Tammi: Good point, playing around with that idea I agree with you now. So I am now completely behind supporting your method of:
offspringStat = randomParentStat+noise.
Are there any other ideas floating around though?
I completely agree with the mathematical niceness of handling the edge cases like you two are considering, but I am not convinced it is necessary. (And worry it is likely to scare away the Mojang Programmers? I do not know how much math they like?)
Consider the example:
Let the scale of stats be 0-10, and choose a bell curve with std dev 0.5, a pretty large standard deviation in my mind. Half the time the stat will change by more than ~0.3. A quarter of the time it will change by more than ~0.6. This seems reasonable to me?
If your horse is 9, then ~2.5% of offspring would be perfect. If we consider all three traits being 9, then the offspring will be perfect ~0.0015%.
If your horse is 9.5, then ~15% perfect. All three traits being 9.5, perfect offspring in all three ~0.3%.
If your horse is 10, then 50% perfect. With three traits 12.5% will come out perfect.
So to get a perfect horse it seems like it would still take a bit of work without limiting gains near the top.
So sure, by not dealing with the edge cut off, a perfect horse is attainable, but depending on the kind of standard deviation that Mojang wants and if they are ok with people getting perfect horses, it may not be necessary? So I am a fan of this approach as a first attempt:
If it doesn't work out in testing because it's too easy to get a perfect horse, or they don't want perfect to be attainable, then try the more complicated thing.
I wonder if this thread is so long that everyone from Mojang stopped reading?