What the WCA rules require (why these changes matter) • Average of 5 (Ao5): remove the best and worst; one DNF/DNS may count as the worst; with 2+ DNF/DNS the average is DNF.  • Mean of 3 (Mo3): any DNF/DNS ⇒ mean is DNF.  • Rounding: attempts are recorded to hundredths under 10:00; averages/means are rounded to nearest hundredth under 10:00 and to the nearest second above 10:00. (This is easy to overlook!)  Review of your code • ✅ Logic for Ao5 with 0 or 1 DNF is conceptually right, and Mo3 returns DNF if any null—good. • ⚠️ Rounding not applied per WCA (nearest 0.01s under 10:00; nearest second ≥ 10:00). • ⚠️ Multiple passes over the list (sum, min, max)—fine for 5 items, but trivial to make single-pass. • ⚠️ Implicit size assumptions. solveAverage() should assert exactly 5 attempts; solveMean() should assert 3 attempts, otherwise unexpected input can sneak through. • ⚠️ Floating-point drift. Using Double math for 0.01s rounding can bite (e.g., 1.23*100 ≈ 122.999…). Prefer integer centiseconds or BigDecimal. • ⚠️ Validation. Guard against negatives / NaN. • 🤏 Readability: explicit names like averageAo5 / meanMo3 make intent clear and encode the rule in the signature. A tight, robust implementation (Kotlin 2.2.x) This version: • Uses centiseconds (cs) for arithmetic to avoid FP error. • Does one pass to compute sum/min/max and DNF count. • Applies the exact WCA rounding rules to the final average/mean. • Enforces expected attempt counts. ```kt private const val EPS = 1e-9 private fun toCentiseconds(timeSec: Double): Long { require(timeSec >= 0.0 && !timeSec.isNaN()) { "Time must be a non-negative number" } // WCA attempts are truncated to hundredths under 10:00; if your inputs already are, // this preserves them. EPS avoids 122.999... from 1.23 * 100. return kotlin.math.floor(timeSec * 100.0 + EPS).toLong() } /** Round a total (in centiseconds) divided by n attempts per WCA 9f1. */ private fun roundAverage(totalCs: Long, n: Int): Double { // If the (true) average >= 600s, round to nearest second; else nearest hundredth. val threshold = 60000L * n // 600s * 100cs/s * n return if (totalCs >= threshold) { // nearest second: divide by (n*100), half-up val sec = (totalCs + (n * 100) / 2) / (n * 100) sec.toDouble() } else { // nearest hundredth: divide by n cs, half-up val cs = (totalCs + n / 2) / n cs / 100.0 } } fun List<Double?>.averageAo5(): Double? { require(size == 5) { "Average of 5 requires exactly 5 attempts." } var dnfs = 0 var sumCs = 0L var minCs = Long.MAX_VALUE var maxCs = Long.MIN_VALUE for (t in this) { if (t == null) { dnfs++ continue } val cs = toCentiseconds(t) sumCs += cs if (cs < minCs) minCs = cs if (cs > maxCs) maxCs = cs } if (dnfs >= 2) return null // 9f9 val usedTotalCs = if (dnfs == 0) { // remove best and worst sumCs - minCs - maxCs } else { // one DNF counts as worst; remove best only sumCs - minCs } // average over 3 results (size - 2) return roundAverage(usedTotalCs, 3) } fun List<Double?>.meanMo3(): Double? { require(size == 3) { "Mean of 3 requires exactly 3 attempts." } var sumCs = 0L for (t in this) { if (t == null) return null // 9f11 sumCs += toCentiseconds(t) } return roundAverage(sumCs, 3) } ``` Notes & optional tweaks • If you also want to represent +2 penalties, keep your Double inputs as “final times including any penalties”. If you want stronger typing, model attempts as a sealed type (Time, DNF, DNS) and map to nullable Double at the edges. • If you prefer BigDecimal over integer centiseconds, use it only at the final rounding step (it’s heavier than ints but fine for 3–5 items). • If these run in a hot path, you can mark helpers @JvmInline value classes for cs/seconds, but for Ao5/Mo3 it won’t matter. Why this is “correct” • Ao5 keep/drop rules and DNF handling match 9f8–9f9; Mo3 DNF handling matches 9f10–9f11; rounding matches 9f1 (nearest 0.01s under 10:00; nearest second ≥ 10:00).  If you want, toss me a couple real input examples (including a ≥10:00 case and a “with +2” case) and I’ll sanity-check the outputs against the regs.