Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Eval Pane4: don't draw offset histogram if no data
Bran has been having an issue with Eval Pane4's offset histogram after upgrading to SL 4.8.3 where the histogram draws as enormously large when no offset data exists (when every judgment was a Miss). Although I'm completely unable to recreate this, it caused me to reexamine the code involved. I concluded I was doing a lot of unnecessary work when no offset data was available and I've cleaned up the code accordingly. Now, if no offset data is available, we don't bother trying to smooth non-existent data, we don't add an empty AMV to the screen, we don't bother calculating mean/median/mode, etc. This is a good thing to do regardless, and maybe it will resolve whatever is going on with Bran's setup.
- Loading branch information
1 parent
c13e3d3
commit 58b3df2
Showing
2 changed files
with
204 additions
and
179 deletions.
There are no files selected for viewing
198 changes: 198 additions & 0 deletions
198
BGAnimations/ScreenEvaluation common/PerPlayer/Pane4/Calculations.lua
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,198 @@ | ||
local args = ... | ||
local offsets, worst_window, pane_width, pane_height = args[1], args[2], args[3], args[4] | ||
|
||
-- --------------------------------------------- | ||
-- FIXME: Smoothing the histogram is good overall, but high-level tech players have noted that their | ||
-- Quad Star histograms are wider than they should be, sometimes even "bleeding into gold territory!" | ||
-- | ||
-- Although this feature was designed to help new players establish a sense of timing more quickly | ||
-- (i.e., it was not for high-level players consistently earning Quad Stars), they have a valid complaint. | ||
-- | ||
-- For now, I'm keeping the smoothing procedure in place, because the graphs new players tend to generate | ||
-- are typically very jagged, causing the intent of the graph (to help) to become lost in the noise. | ||
-- | ||
-- For more, consult the dedication in House of Leaves. | ||
|
||
-- --------------------------------------------- | ||
-- smooth the offset distribution and store values in a new table, smooth_offsets | ||
local smooth_offsets = {} | ||
|
||
-- gaussian distribution for smoothing the histogram's jagged peaks and troughs | ||
local ScaleFactor = { 0.045, 0.090, 0.180, 0.370, 0.180, 0.090, 0.045 } | ||
|
||
local y, index | ||
for offset=-worst_window, worst_window, 0.001 do | ||
offset = round(offset,3) | ||
y = 0 | ||
|
||
-- smooth like butter | ||
for j=-3,3 do | ||
index = clamp( offset+(j*0.001), -worst_window, worst_window ) | ||
index = round(index,3) | ||
if offsets[index] then | ||
y = y + offsets[index] * ScaleFactor[j+4] | ||
end | ||
end | ||
|
||
smooth_offsets[offset] = y | ||
end | ||
|
||
-- --------------------------------------------- | ||
-- MEDIAN, MODE, and AVG TIMING ERROR VARIABLES | ||
-- initialize all to zero | ||
|
||
-- mode_offset is the offset that occured the most commonly | ||
-- for example, if a player hit notes with an offset of -0.010 | ||
-- more commonly than any other offset, that would be the mode | ||
local mode_offset = 0 | ||
|
||
-- median_offset is the offset in the middle of an ordered list of all offsets | ||
-- 2 is the median in a set of { 1, 1, 2, 3, 4 } because it is in the middle | ||
local median_offset = 0 | ||
|
||
-- highest_offset_count is how many times the mode_offset occurred | ||
-- we'll use it to scale the histrogram to be an appropriate height | ||
local highest_offset_count = 0 | ||
|
||
local sum_timing_error = 0 | ||
local avg_timing_error = 0 | ||
|
||
-- --------------------------------------------- | ||
-- OKAY, TIME TO CALCULATE MEDIAN, MODE, and AVG TIMING ERROR | ||
|
||
-- find the mode of the collected judgment offsets for this player | ||
-- loop through ALL offsets | ||
for k,v in pairs(offsets) do | ||
|
||
-- compare this particular offset to the current highest_offset | ||
-- if higher, it's the new mode | ||
if v > highest_offset_count then | ||
highest_offset_count = v | ||
mode_offset = round(k,3) | ||
end | ||
end | ||
|
||
-- transform a key=value table in the format of offset_value=count | ||
-- into an ordered list of offset values | ||
-- this will make calculating the median very straightforward | ||
local list = {} | ||
for offset=-worst_window, worst_window, 0.001 do | ||
offset = round(offset,3) | ||
|
||
if offsets[offset] then | ||
for i=1,offsets[offset] do | ||
list[#list+1] = offset | ||
end | ||
end | ||
end | ||
|
||
if #list > 0 then | ||
|
||
-- calculate median offset | ||
if #list % 2 == 1 then | ||
median_offset = list[math.ceil(#list/2)] | ||
else | ||
median_offset = (list[#list/2] + list[#list/2+1])/2 | ||
end | ||
|
||
-- loop throguh all offsets collected | ||
-- take the absolute value (because this offset could be negative) | ||
-- and add it to the running measure of total timing error | ||
for i=1,#list do | ||
sum_timing_error = sum_timing_error + math.abs(list[i]) | ||
end | ||
|
||
-- calculate the avg timing error, rounded to 3 decimals | ||
avg_timing_error = round(sum_timing_error/#list,3) | ||
end | ||
-- --------------------------------------------- | ||
|
||
-- --------------------------------------------- | ||
-- Calculate vertices for Histogram AMV | ||
|
||
local verts = {} | ||
|
||
-- total_width of the histogram in offset units | ||
-- take the number of milliseconds in worst_window | ||
-- multiply by 2 (to encompass both negative and positive judgment offsets) | ||
-- multiply by 1000 to get an integer | ||
-- + 1 for the offset of 0.000 | ||
local total_width = worst_window * 2 * 1000 + 1 | ||
|
||
-- w is a ratio of how wide the pane is in pixels | ||
-- to how wide the total TimingWindow interval is in ms | ||
-- so, pixels per ms | ||
local w = pane_width/total_width | ||
|
||
-- x and c are variables that will be reused in the loop below | ||
-- x is the x position of this particular histogram bar | ||
-- c is the color of this particular histogram bar | ||
local x, c | ||
|
||
local i=1 | ||
for offset=-worst_window, worst_window, 0.001 do | ||
offset = round(offset,3) | ||
x = i * w | ||
y = smooth_offsets[offset] or 0 | ||
|
||
-- scale the highst point on the histogram to be 0.75 times as high as the pane | ||
y = -1 * scale(y, 0, highest_offset_count, 0, pane_height*0.75) | ||
c = SL.JudgmentColors[SL.Global.GameMode][DetermineTimingWindow(offset)] | ||
|
||
-- the ActorMultiVertex is in "QuadStrip" drawmode, like a series of quads placed next to one another | ||
-- each vertex is a table of two tables: | ||
-- {x, y, z}, {r, g, b, a} | ||
verts[#verts+1] = {{x, 0, 0}, c } | ||
verts[#verts+1] = {{x, y, 0}, c } | ||
|
||
i = i+1 | ||
end | ||
|
||
|
||
-- -------------------------------------------------------- | ||
local af = Def.ActorFrame{} | ||
|
||
-- -------------------------------------------------------- | ||
-- LOOK AT THIS GRAPH | ||
|
||
-- the histogram AMV | ||
af[#af+1] = Def.ActorMultiVertex{ | ||
Name="ModeJudgmentOffset_AMV", | ||
OnCommand=function(self) | ||
self:SetDrawState{Mode="DrawMode_QuadStrip"} | ||
:SetVertices(verts) | ||
end | ||
} | ||
|
||
-- -------------------------------------------------------- | ||
-- avg_timing_error value | ||
af[#af+1] = Def.BitmapText{ | ||
Font="_miso", | ||
Text=(avg_timing_error*1000).."ms", | ||
InitCommand=function(self) | ||
self:x(40):y(-pane_height+32) | ||
:zoom(0.8) | ||
end, | ||
} | ||
|
||
-- median_offset value | ||
af[#af+1] = Def.BitmapText{ | ||
Font="_miso", | ||
Text=(median_offset*1000).."ms", | ||
InitCommand=function(self) | ||
self:x(pane_width/2):y(-pane_height+32) | ||
:zoom(0.8) | ||
end, | ||
} | ||
|
||
-- mode_offset value | ||
af[#af+1] = Def.BitmapText{ | ||
Font="_miso", | ||
Text=(mode_offset*1000).."ms", | ||
InitCommand=function(self) | ||
self:x(pane_width-40):y(-pane_height+32) | ||
:zoom(0.8) | ||
end, | ||
} | ||
|
||
return af |
Oops, something went wrong.