From 0c7fc8a315c3fccb7a1deeaf6de7406a6657d2c0 Mon Sep 17 00:00:00 2001 From: Luke Policinski Date: Mon, 2 Nov 2026 20:02:53 -0500 Subject: [PATCH 1/2] bug: the flash card counts a target blind 1s+ by the seconds it shows, so a 3.0s flash no longer reads 0 of 1 --- apps/utility-sw/src/Hud/PracticeFlash.cs | 23 +++++++------- .../test/PracticeCardGroupsTests.cs | 30 +++++++++++++++++-- .../FiveStack.Utilities/PracticeCardGroups.cs | 14 ++++++--- 3 files changed, 47 insertions(+), 20 deletions(-) diff --git a/apps/utility-sw/src/Hud/PracticeFlash.cs b/apps/utility-sw/src/Hud/PracticeFlash.cs index 508e2161..5d311f8a 100644 --- a/apps/utility-sw/src/Hud/PracticeFlash.cs +++ b/apps/utility-sw/src/Hud/PracticeFlash.cs @@ -123,18 +123,17 @@ private void RecordFlash(IPlayer thrower, IPlayer blinded, CCSPlayerPawn? pawn, int steps = HudSlots.FlashBarSteps - 1; var rows = new List(); + List hits = report + .Hits.Select(hit => new PracticeCardGroups.FlashHit( + hit.Seconds, + MathF.Max(0f, hit.Seconds - FlashFadeSeconds), + hit.Target, + hit.Bad + )) + .ToList(); if (report.Hits.Count > HudSlots.FlashRows) { - List hits = report - .Hits.Select(hit => new PracticeCardGroups.FlashHit( - hit.Seconds, - MathF.Max(0f, hit.Seconds - FlashFadeSeconds), - hit.Target, - hit.Bad - )) - .ToList(); - foreach (PracticeCardGroups.Group group in PracticeCardGroups.Flash(hits)) { rows.Add( @@ -174,10 +173,8 @@ FlashHit hit in rows.Count > 0 } int targets = report.Hits.Count(hit => hit.Target); - int worked = report.Hits.Count(hit => - hit.Target && hit.Seconds - FlashFadeSeconds >= PracticeCardGroups.WorkedWhiteSeconds - ); - string foot = targets == 0 ? "No targets caught" : $"{worked} of {targets} fully blind 1s+"; + int worked = PracticeCardGroups.Worked(hits); + string foot = targets == 0 ? "No targets caught" : $"{worked} of {targets} blind 1s+"; if (report.Hits.Any(hit => hit.Bad)) { diff --git a/apps/utility-sw/test/PracticeCardGroupsTests.cs b/apps/utility-sw/test/PracticeCardGroupsTests.cs index ff17aaf9..b4cd0658 100644 --- a/apps/utility-sw/test/PracticeCardGroupsTests.cs +++ b/apps/utility-sw/test/PracticeCardGroupsTests.cs @@ -20,10 +20,34 @@ public void AFlashOnABoxOfBotsIsCountedByWhatItDid() IReadOnlyList groups = PracticeCardGroups.Flash(hits); - Assert.Equal(new[] { "fully blind", "dazzled", "your side" }, groups.Select(group => group.Label)); - Assert.Equal(new[] { 2, 3, 1 }, groups.Select(group => group.Count)); + Assert.Equal(new[] { "blinded", "your side" }, groups.Select(group => group.Label)); + Assert.Equal(new[] { 5, 1 }, groups.Select(group => group.Count)); Assert.Equal(4.8f, groups[0].Seconds); - Assert.True(groups[2].Bad); + Assert.True(groups[1].Bad); + } + + [Fact] + public void ThreeSecondsOfBlindIsOverASecondBlind() + { + var hits = new List + { + new(3.0f, 0f, Target: true, Bad: false), + }; + + Assert.Equal(1, PracticeCardGroups.Worked(hits)); + Assert.Equal(new[] { ("blinded", 1) }, PracticeCardGroups.Flash(hits).Select(group => (group.Label, group.Count))); + } + + [Fact] + public void UnderASecondOrOnYourOwnSideIsNotAWorkedFlash() + { + var hits = new List + { + new(0.9f, 0f, Target: true, Bad: false), + new(4.0f, 1f, Target: false, Bad: true), + }; + + Assert.Equal(0, PracticeCardGroups.Worked(hits)); } [Fact] diff --git a/shared/dotnet/FiveStack.Utilities/PracticeCardGroups.cs b/shared/dotnet/FiveStack.Utilities/PracticeCardGroups.cs index 44b999cd..089d03e6 100644 --- a/shared/dotnet/FiveStack.Utilities/PracticeCardGroups.cs +++ b/shared/dotnet/FiveStack.Utilities/PracticeCardGroups.cs @@ -5,13 +5,19 @@ namespace FiveStack.Utilities; // them rather than listed by name. public static class PracticeCardGroups { - // Blind for at least this long with nothing to see is a flash that worked. - public const float WorkedWhiteSeconds = 1f; + // Judged on the same seconds the card shows, or a row reading 3.0s sits + // above a foot saying nobody was blind for one. + public const float WorkedSeconds = 1f; public record FlashHit(float Seconds, float White, bool Target, bool Bad); public record Group(string Label, int Count, float Seconds, float White, bool Bad); + public static int Worked(IReadOnlyList hits) + { + return hits.Count(hit => hit.Target && hit.Seconds >= WorkedSeconds); + } + public static IReadOnlyList Flash(IReadOnlyList hits) { var groups = new List(); @@ -28,8 +34,8 @@ void Add(string label, IEnumerable members, bool bad) } } - Add("fully blind", hits.Where(hit => hit.Target && hit.White >= WorkedWhiteSeconds), false); - Add("dazzled", hits.Where(hit => hit.Target && hit.White < WorkedWhiteSeconds), false); + Add("blinded", hits.Where(hit => hit.Target && hit.Seconds >= WorkedSeconds), false); + Add("dazzled", hits.Where(hit => hit.Target && hit.Seconds < WorkedSeconds), false); Add("your side", hits.Where(hit => hit.Bad), true); return groups; From 26285e28682e108c0554ac64bc4703faac5e5b10 Mon Sep 17 00:00:00 2001 From: Luke Policinski Date: Mon, 2 Nov 2026 20:14:01 -0500 Subject: [PATCH 2/2] bug: a flash row that reads 1.0s counts as 1s on the card foot --- apps/utility-sw/src/Hud/PracticeFlash.cs | 2 +- .../test/PracticeCardGroupsTests.cs | 21 +++++++++++++++++++ .../FiveStack.Utilities/PracticeCardGroups.cs | 11 +++++++--- 3 files changed, 30 insertions(+), 4 deletions(-) diff --git a/apps/utility-sw/src/Hud/PracticeFlash.cs b/apps/utility-sw/src/Hud/PracticeFlash.cs index 5d311f8a..484677e8 100644 --- a/apps/utility-sw/src/Hud/PracticeFlash.cs +++ b/apps/utility-sw/src/Hud/PracticeFlash.cs @@ -6,7 +6,7 @@ namespace UtilityPractice; // What a flash did: everybody it caught, for how long, and how much of that // was seeing nothing at all. One number in chat said a flash had landed; it -// did not say on whom, or whether "2.1 seconds" meant blind or merely dazzled. +// did not say on whom. public partial class UtilityPracticePlugin { private class FlashHit diff --git a/apps/utility-sw/test/PracticeCardGroupsTests.cs b/apps/utility-sw/test/PracticeCardGroupsTests.cs index b4cd0658..0da3fd27 100644 --- a/apps/utility-sw/test/PracticeCardGroupsTests.cs +++ b/apps/utility-sw/test/PracticeCardGroupsTests.cs @@ -50,6 +50,27 @@ public void UnderASecondOrOnYourOwnSideIsNotAWorkedFlash() Assert.Equal(0, PracticeCardGroups.Worked(hits)); } + [Fact] + public void ARowThatReadsASecondCountsAsASecond() + { + Assert.Equal(1, PracticeCardGroups.Worked(new List { new(0.96f, 0f, Target: true, Bad: false) })); + Assert.Equal(1, PracticeCardGroups.Worked(new List { new(1f, 0f, Target: true, Bad: false) })); + Assert.Equal(0, PracticeCardGroups.Worked(new List { new(0.94f, 0f, Target: true, Bad: false) })); + } + + [Fact] + public void UnderASecondIsDazzledAndStillOnTheCard() + { + var hits = new List + { + new(3.0f, 0f, Target: true, Bad: false), + new(0.8f, 0f, Target: true, Bad: false), + new(0.6f, 0f, Target: true, Bad: false), + }; + + Assert.Equal(new[] { ("blinded", 1), ("dazzled", 2) }, PracticeCardGroups.Flash(hits).Select(group => (group.Label, group.Count))); + } + [Fact] public void AFireCountsWhoItReachedNotHowMuch() { diff --git a/shared/dotnet/FiveStack.Utilities/PracticeCardGroups.cs b/shared/dotnet/FiveStack.Utilities/PracticeCardGroups.cs index 089d03e6..fd374880 100644 --- a/shared/dotnet/FiveStack.Utilities/PracticeCardGroups.cs +++ b/shared/dotnet/FiveStack.Utilities/PracticeCardGroups.cs @@ -15,7 +15,12 @@ public record Group(string Label, int Count, float Seconds, float White, bool Ba public static int Worked(IReadOnlyList hits) { - return hits.Count(hit => hit.Target && hit.Seconds >= WorkedSeconds); + return hits.Count(hit => hit.Target && Blinded(hit)); + } + + private static bool Blinded(FlashHit hit) + { + return MathF.Round(hit.Seconds, 1, MidpointRounding.AwayFromZero) >= WorkedSeconds; } public static IReadOnlyList Flash(IReadOnlyList hits) @@ -34,8 +39,8 @@ void Add(string label, IEnumerable members, bool bad) } } - Add("blinded", hits.Where(hit => hit.Target && hit.Seconds >= WorkedSeconds), false); - Add("dazzled", hits.Where(hit => hit.Target && hit.Seconds < WorkedSeconds), false); + Add("blinded", hits.Where(hit => hit.Target && Blinded(hit)), false); + Add("dazzled", hits.Where(hit => hit.Target && !Blinded(hit)), false); Add("your side", hits.Where(hit => hit.Bad), true); return groups;