From 37edae9af846e586016ef57b30d45f28a0586f54 Mon Sep 17 00:00:00 2001 From: "born a rick, raised a morty, died a jerry" Date: Mon, 11 May 2020 11:23:20 -0400 Subject: [PATCH] begin new jack stuff --- src/Etterna/Globals/MinaCalc.cpp | 220 +++++++++++++++++++++++-------- src/Etterna/Globals/MinaCalc.h | 2 +- 2 files changed, 163 insertions(+), 59 deletions(-) diff --git a/src/Etterna/Globals/MinaCalc.cpp b/src/Etterna/Globals/MinaCalc.cpp index fcac029289..2b5146be76 100644 --- a/src/Etterna/Globals/MinaCalc.cpp +++ b/src/Etterna/Globals/MinaCalc.cpp @@ -271,6 +271,33 @@ Hand::InitPoints(const Finger& f1, const Finger& f2) f2[ki_is_rising].size()); } #pragma endregion utils + +// DON'T WANT TO RECOMPILE HALF THE GAME IF I EDIT THE HEADER FILE +static const float finalscaler = + 2.564f * 1.05f * 1.1f * 1.10f * 1.115f; // multiplier to standardize baselines + +// ***note*** if we want max control over stamina we need to have one model for +// affecting the other skillsets to a certain degree, enough to push up longer +// stream ratings into contention with shorter ones, and another for both a more +// granular and influential modifier to calculate the end stamina rating with +// so todo on that + +// Stamina Model params +static const float stam_ceil = 1.065234f; // stamina multiplier max +static const float stam_mag = 273.f; // multiplier generation scaler +static const float stam_fscale = 500.f; // how fast the floor rises (it's lava) +static const float stam_prop = + 0.69424f; // proportion of player difficulty at which stamina tax begins + +// since we are no longer using the normalizer system we need to lower +// the base difficulty for each skillset and then detect pattern types +// to push down OR up, rather than just down and normalizing to a differential +// since chorded patterns have lower enps than streams, streams default to 1 +// and chordstreams start lower +// stam is a special case and may use normalizers again +static const float basescalers[NUM_Skillset] = { 0.f, 0.97f, 0.89f, 0.88f, + 0.94f, 0.9f, 0.84f, 0.88f }; + #pragma region CalcBodyFunctions float Calc::JackLoss(const vector& j, float x) @@ -278,45 +305,147 @@ Calc::JackLoss(const vector& j, float x) float o = 0.f; for (size_t i = 0; i < j.size(); i++) if (x < j[i]) - o += 7.f - (7.f * fastpow(x / (j[i] * 0.88f), 1.7f)); + o += 7.f - (17.f * fastpow(x / (j[i] * 1.88f), 1.7f)); CalcClamp(o, 0.f, 10000.f); return o; } +inline float +ms_to_bpm(float& x) +{ + return 15000.f / x; +} + JackSeq Calc::SequenceJack(const vector& NoteInfo, unsigned int t, - float music_rate) + float music_rate, + bool debugmode) { - vector output; - float last = -5.f; - float interval1 = 250.f; - float interval2 = 250.f; - float interval3 = 250.f; - float interval4 = 250.f; + bool dbg = true && debugmode; + // the 4 -> 5 note jack difficulty spike is well known, we aim to reflect + // this phenomena as best as possible. 500, 50, 50, 50, 50 should end up + // significantly more difficult than 50, 50, 50, 50, 50 + + // NOTE: in comments here "jack" will refer to exclusively the time between + // two taps on the same column, a "sequence" of jacks is n number of jacks + // which contain n + 1 number of total taps, a "component" of a sequence can + // be any n consecutive jacks within a sequence. we are operating under the + // assumption that the difficulty of any jack in any sequence is entirely + // dependent on the previous components of the sequence, and what comes + // afterwards is not relevant (usually true in actual gameplay, outside of + // stuff like mines immediately after shortjacks) + vector jack_diff; + static const int window_size = 5; + vector window_taps = { -1.f, -2.f, -3.f, -4.f, -5.f }; + vector comp_diff(window_size - 1); + vector eff_scalers(window_size - 1); + float last_time = -1.f; unsigned int track = 1u << t; + for (auto r : NoteInfo) { + float last_diff = 0.f; + if (r.notes & track) { + // convert to ms from s, it's much more intuitive + float cur_time = r.rowTime / music_rate * 1000.f; + + if (dbg) + std::cout << "time now: " << cur_time / 1000.f << std::endl; + // update most recent values + for (size_t i = 1; i < window_taps.size(); ++i) { + if (false) { + std::cout << "window tap at: " << i - 1 + << " time was: " << window_taps[i - 1] + << std::endl; + std::cout << "window tap at: " << i + << " time is: " << window_taps[i] << std::endl; + std::cout << "difference is: " + << window_taps[i] - window_taps[i - 1] + << std::endl; + } + window_taps[i - 1] = window_taps[i]; + } + + // new plan, store cur time so we can easily grab the time the last + // 5 hits take place over, if we need specific ms values, we can + // derive them + window_taps[window_size - 1] = cur_time; + float window_time = window_taps.back() - window_taps.front(); + if (dbg) + std::cout << "cur window time: " << window_time / 1000.f << std::endl; + + // yes this is many loops, but we don't want to sacrifice + // legitimately difficult minijacks in the name of proper evaluation + // of shortjacks and longjacks, so try scanning through the last 5 + // in a minisequence to pick out something something? + for (size_t i = 1; i < window_taps.size(); ++i) { + // first jack is element 1 - 0, 2nd is 2 - 1, 3rd is 3 - 2, and + // 4th is 4 - 3, each with their own total time at n - 0, this + // leaves us with 4 components for jack difficulty estimation + // that will hopefully not underrate minijacks + + float base_ms = window_taps[i] - window_taps[i - 1]; + float comp_time = window_taps[i] - window_taps[0]; + + // we sequence inside the jack this way so that we don't ignore + // minijacks, but we don't want them to overpower things like + // they did in 263. We know the general rules that govern how + // hard minijacks, shortjacks, and longjacks are to hit - an + // isolated minijack has an effective window if 180ms + jack ms + // + 180, an isolated triplet has an effective window of 180 + + // jack ms + jack ms + 180, the latter is more difficult but not + // by much, even if the jacks are extremely fast, since jacks + // are effectively down translated from x bpm jacks to y bpm as + // a function of the miss window + + // try adding 180 for now, and see how well it works + float eff_ms = comp_time + 90.f; + + // compute a simple scaler by taking the effective ms window + // (converted to bpm for the moment for familiarity / clarity) + // remember to multiply effective ms by number of jacks in the + // component + + // we do want the base bpm to be the most recent value, not an + // average, since we will aggregate the value in (some) way at + // the end of this + float base_bpm = ms_to_bpm(base_ms); + float eff_bpm = ms_to_bpm(eff_ms) * static_cast(i); + float eff_scaler = eff_bpm / base_bpm; + + // ok this is pretty tricky, but basically if we catch a + // minijack in otherwise lenient spacing, we still want to + // evaluate it to at least a jack of some significance, we can + // divide by the effective scaler to get an idea of how the + // difficulty of the sequence at each stage is represented, then + // multiply by the final effective scaler for the sequence as a + // whole after aggregating the component estimates + comp_diff[i - 1] = base_bpm / eff_scaler; + eff_scalers[i - 1] = eff_scaler; + if (dbg) { + std::cout << "\nseq component: " << i << std::endl; + std::cout << "base bpm: " << base_bpm << std::endl; + std::cout << "eff bpm: " << eff_bpm << std::endl; + std::cout << "eff scaler: " << eff_scaler << std::endl; + } + } - for (auto i : NoteInfo) { - if (i.notes & track) { - float current_time = i.rowTime / music_rate; - interval1 = interval2; - interval2 = interval3; - interval3 = interval4; - interval4 = 1000.f * (current_time - last); - last = current_time; - output.emplace_back(min( - 2750.f / - min((interval2 + interval3 + interval4) / 3.f, - 0.8f * interval4 * - CalcClamp( - 1.f + cv(vector{ - interval1, interval2, interval3, interval4 }), - 1.f, - 1.8f)), - 45.f)); + float comp_mean = mean(comp_diff); + // convert bpm to nps so we can apply finalscaler and get roughly + // comparable values to other skillsets and use the basescaler lever + // to fine tune + float fdiff = comp_mean / 15.f * finalscaler * mean(eff_scalers) * basescalers[Skill_JackSpeed]; + last_diff = fdiff; + jack_diff.push_back(fdiff); + last_time = cur_time; + if (dbg) { + std::cout << "comp mean: " << comp_mean << std::endl; + std::cout << "fdiff: " << fdiff << std::endl; + std::cout << "finished this sequence \n" << std::endl; + } } } - return output; + + return jack_diff; } Finger @@ -388,32 +517,6 @@ Calc::ProcessFinger(const vector& NoteInfo, return AllIntervals; } -// DON'T WANT TO RECOMPILE HALF THE GAME IF I EDIT THE HEADER FILE -static const float finalscaler = - 2.564f * 1.05f * 1.1f * 1.10f * 1.115f; // multiplier to standardize baselines - -// ***note*** if we want max control over stamina we need to have one model for -// affecting the other skillsets to a certain degree, enough to push up longer -// stream ratings into contention with shorter ones, and another for both a more -// granular and influential modifier to calculate the end stamina rating with -// so todo on that - -// Stamina Model params -static const float stam_ceil = 1.065234f; // stamina multiplier max -static const float stam_mag = 273.f; // multiplier generation scaler -static const float stam_fscale = 500.f; // how fast the floor rises (it's lava) -static const float stam_prop = - 0.69424f; // proportion of player difficulty at which stamina tax begins - -// since we are no longer using the normalizer system we need to lower -// the base difficulty for each skillset and then detect pattern types -// to push down OR up, rather than just down and normalizing to a differential -// since chorded patterns have lower enps than streams, streams default to 1 -// and chordstreams start lower -// stam is a special case and may use normalizers again -static const float basescalers[NUM_Skillset] = { 0.f, 0.97f, 0.89f, 0.88f, - 0.94f, 0.7f, 0.84f, 0.88f }; - vector Calc::CalcMain(const vector& NoteInfo, float music_rate, @@ -676,10 +779,10 @@ Calc::InitializeHands(const vector& NoteInfo, } } - j0 = SequenceJack(NoteInfo, 0, music_rate); - j1 = SequenceJack(NoteInfo, 1, music_rate); - j2 = SequenceJack(NoteInfo, 2, music_rate); - j3 = SequenceJack(NoteInfo, 3, music_rate); + j0 = SequenceJack(NoteInfo, 0, music_rate, debugmode); + j1 = SequenceJack(NoteInfo, 1, music_rate, false); + j2 = SequenceJack(NoteInfo, 2, music_rate, false); + j3 = SequenceJack(NoteInfo, 3, music_rate, false); return true; } @@ -715,7 +818,8 @@ Hand::InitBaseDiff(Finger& f1, Finger& f2) difficulty = (5.f * right_difficulty + 2.f * left_difficulty) / 7.f; soap[BaseNPS][i] = finalscaler * nps; soap[BaseMS][i] = finalscaler * difficulty; - soap[BaseMSD][i] = finalscaler * (2.33333f * difficulty + 6.66666f * nps) / 9.f; + soap[BaseMSD][i] = + finalscaler * (2.33333f * difficulty + 6.66666f * nps) / 9.f; } Smooth(soap[BaseNPS], 0.f); if (SmoothDifficulty) diff --git a/src/Etterna/Globals/MinaCalc.h b/src/Etterna/Globals/MinaCalc.h index 318be095da..81e4402829 100644 --- a/src/Etterna/Globals/MinaCalc.h +++ b/src/Etterna/Globals/MinaCalc.h @@ -116,7 +116,7 @@ class Calc static float JackLoss(const std::vector& j, float x); static JackSeq SequenceJack(const std::vector& NoteInfo, unsigned int t, - float music_rate); + float music_rate, bool debugmode); bool debugmode = false; bool capssr = true; // set to true for scores, false for cache