-
Notifications
You must be signed in to change notification settings - Fork 45
Continuous Difficulty Calcuation #86
Comments
Please explain how you got here. |
...That’s literally just converting the strains into a function to manipulate. Kind of like the code in a formulaic notation. I just turned the discrete equation into a continuous one. |
Repository with some of the files: |
Here are some aim star changes |
SeemsGood |
I've gone through your post/R code and checked through all the maths. Amazing. BTW you had to add a negative in the ds formula because you swapped the limits of integration around :) For those less mathematically inclined, he's very cleverly calculating what the current code would do with an infinitely small section_length, rather than a section_length of 400ms. It'd actually be a good way to test if everything is working. In the original C# code, set the section_length to 1ms or so and fix the DifficultyValue function to be independent of section_length (just change the 0.9 to (0.9^(section_length/400), and rescale at the end). It'll be slower than what you've got but it should give a very similar result. BTW I think I've found a subtle error: you're using decay[i] to calculate infimum[i], but thats based on the delta time from the previous hit object to the current, not the time from the current to the next. I think you should use decay[i+1]. You'd have to add a bit of time onto the end so that the last strain has time to decay and contribute to the integral (or you could take the limit to infinity for even more fun!). There's also one weirdness in your implementation: when calculating the CDF you recalculate the total from scratch every time rather than reusing the partial sums to do it in one pass. I also don't like the is.element() stuff, I'd rather have a separate column in the sorted data to distinguish inf and sup, but that doesn't matter as much, and I guess it's just a prototype so neither of these is a big deal. Also there was some stuff on discord about removing the breaks to get a more accurate result. I don't think that should affect things, since the lowest strains should have negligible weight. I think you're forgetting to update the total time somewhere when you're manually truncating sections, so you're kind of stretching the map out and weighting the hard stuff more than you should. Now for my personal opinion: I'm also unsure if this whole thing is a good idea to begin with. It's insanely more complex than what we've currently got and will potentially make future changes much harder. There'll be work to do rebalancing to take into account the decreased star ratings, and there isn't even necessarily a concrete gain vs the old system. And even if there was, we could almost fully realise it by just decreasing section_length a bit. This is definitely more elegant, but in software engineering, simplicity trumps elegance every time. I'd say its more of a performance optimisation if it is decided that decreasing section_length is even a good idea. We'd need to benchmark both and show that the speedup is worth the increased complexity. |
joseph-Ireland, Thanks for the response.
I took another look at the length bonus, and admit its not really all the way there and doesn't address all the problems length bonus currently has. Though I think the
I argue the opposite.
Thank you for your comment, I am so glad to get specific critiques because it's my first time doing something like this. I would be glad to discuss this more in the future. |
The total time shouldn't be a problem. Suppose you add a zero strain section the length of the whole map. That would make the length double, i.e. If you plug both of those into so the integral is unchanged if you add blank sections. In reality breaks will have a very small strain, not zero, but the results will be extremely close.
I feel like this is a bit disingenuous. It's a continuous function. You spend infinitely more time not pressing buttons in between pressing buttons, and you are counting all the gaps in between as star rating, why not the gap after the last note? As per the previous point, adding empty sections doesn't affect the end result, so you're pretty much just clipping off the last note for no reason. Yes, a limit to infinity is a bit silly, but it's doable! Not worth the hassle though, since it'll hardly change the end result. Not sure about adding strains[1]=1, doesn't seem like it'd change anything significantly, but I don't see the reasoning either.
showing some bias here, but my star rating changes here which replace length bonus don't effortlessly fit in, because it gets rid of the 0.9^n stuff entirely. I'm sure something similar could fit in, but again, not sure if it's worth it. I've not had much time to work on it, but I've recently made some balancing tweaks that I'm close to pushing up and I'm very pleased with the results so far.
I can see where you're going with that, but I'll be honest, I clicked the link and I've got no idea what's going on there. I'm sure you could explain it to me in 15 minutes face-to-face with a blackboard, but I'm confident I could understand the current slider logic just by reading the code just by looking at it, and you're not always going to be there to explain stuff to future maintainers. If I were you I'd look to make some nicely typeset documents explaining your derivations, let's say at a level where a first/second year maths undergrad could understand. Don't skip any steps, and link to relevant web pages where people can find out more about particular methods you are using. Otherwise most people aren't going to have a clue what's going on, and it's hard for the osu team to merge in code when they have no idea how it works, and no way of verifying that it's working as intended. I guess I should also follow my own advice here :) BTW, I'm more of an outsider than you here, so everything I've said is just my honest opinion, which doesn't really carry much weight on its own. edit:formatting |
I guess what I’m afraid of is something like this: Infinite time is not relevant after looking back at it. I just forgot that, the time part cancels when you divide it by the weight because I look at those two in separate areas of my code.
I guess, even so that bias was not without merit because it remains true (thus far). I just tried yours out. It’s even simpler than mine tbh. Was very straight forward and worth it. I really should make a step by step to transform strain sum (and functions of them) to continuous functions. Since this is a difficulty calculation, naturally you would replace the one I had with yours (I just did the equivalent of the old system after all). After following all of my steps up until finding dt in terms of d(strain) (or whatever variable you have for that), apply your formula. Assuming it doesn’t overload, it’s very doable.
I know, that was just me graphing and testing it out and not an explanation of it at all. Just wanted to demonstrate how easy it is to solve problems such as slider strain by extrapolating the information we have when it is continuous.
I guess, though I’m a third year undergrad myself so I don’t think I have an idea of what a 1st or 2nd year undergrad’s knowledge would be. A year ago, the only thing I really did not know on my original comment was the statistics and some of the topology stuff. Everything else is diffEQ or lower, which I think is 2nd year level (right?) |
The equation for your diff system in a Continuous manner is |
(It's technically not continuous because of the Heaviside step functions involved when adding spacing weights, but whatever)
About 2 days ago someone on reddit mentioned to me that Xexxar was trying to do per hit object difficulty calculation. I was surprised when this hadn't been done before, so I decided to take a crack at it.
I'm going to try to keep this as simple as I can
Using the decay and strains, you can easily create and solve a diffEQ to model strain.
ds/dt = ln(0.15)/1000*s+ 26.25*sum( (D_i^0.99/∆t_i) * Dirac Delta(t-t_i) )
where ∆t_i is the delta time associated with a hit object
t_i is the time associated with the hit object
D_i is the distance associated with the hit object
s is strain
(you can actually use this to create a slider rework but that is a discussion for another time)
From there, I created sets of strains from the hit objects until right before the next one. Note that these sets are continuous along the real number line. I took all the infimums and supremums of those sets and used those to calculate the frequency that a certain strain occurs (this is probably the thing I'm most proud of in this entire thing). The frequency shows how many times the strain function hits a certain strain. It is the amount of elements in the intersection between the collection of strain sets and a certain strain. Then I inverted the homogenous portion of the strain diffEQ to find the differential time at each strain.
ds/dt=ln(0.15)/1000*s => dt=-1000/ln(0.15) * ds/s
you have to add a negative because why would you want negative time?
The strain function spends dt time at strain s.
To get the probability density function, you multiply the frequency of a strain multiplied by the differential time, then divide the whole thing by the total time of the beatmap in milliseconds. You integrate that to get the cumulative density function.
Something like:
CDF(s)=-1000/(T*ln(0.15))*sum(frequency(s)*H(s-x_i)*ln(min(s,x_(i+1))/x_i)
which is the probability that the strain in the beatmap is less than or equal to s
where T = total time of beatmap
s = strain
x_i & x_(i+1) are discrete strains from hit objects
H(x) is the Heaviside step function
After this, you space out the probabilities evenly and add to find your weighted strain or integrate
weighted avg strain=
integral(s * 0.9^((1-CDF(s))*N)ds)/integral(0.9^((1-CDF(s))*N)ds)
which is fairly easy because a lot cancels (and a lot is constant) and the exponential of a logarithm is a power.
I already implemented this in R.
Edit: Note that all of the 0.15 can be replaced with 0.3 for speed or any other decay constant
Edit:fixed particular on first EQ
Edit 3: fixed weighted strain integral
The text was updated successfully, but these errors were encountered: