From fbd758d96eb81488e67657b442dc0224b9b5ed35 Mon Sep 17 00:00:00 2001 From: Till JS Date: Mon, 25 May 2026 16:50:05 +0200 Subject: [PATCH] fix(fsrs): 19-Gewichte (FSRS-5) migrieren statt w[20]-Crash MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit FSRS.init: 19-Element-w wird auf 21 erweitert (FSRS-6-Decay-Defaults), unerwartete Längen fallen sicher auf defaultW zurück. Verhindert Index-Crash, falls ein Deck FSRS-5-Settings mit 19 Gewichten liefert. Test mit 19-Gewichte-Settings. Co-Authored-By: Claude Opus 4.7 (1M context) --- Sources/Core/Domain/FSRSScheduler.swift | 18 ++++++++++++++---- Tests/UnitTests/FSRSSchedulerTests.swift | 10 ++++++++++ 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/Sources/Core/Domain/FSRSScheduler.swift b/Sources/Core/Domain/FSRSScheduler.swift index b88488c..9174de5 100644 --- a/Sources/Core/Domain/FSRSScheduler.swift +++ b/Sources/Core/Domain/FSRSScheduler.swift @@ -73,10 +73,20 @@ struct FSRS { private let intervalModifier: Double init(_ settings: FSRSSettings = FSRSSettings()) { - s = settings - decay = -settings.w[20] - factor = Self.roundTo(exp((1.0 / (-settings.w[20])) * log(0.9)) - 1, 8) - intervalModifier = Self.roundTo((pow(settings.requestRetention, 1.0 / (-settings.w[20])) - 1) / factor, 8) + var resolved = settings + // FSRS-5-Settings haben 19 Gewichte → auf FSRS-6 (21) migrieren, + // sonst crasht `w[20]`. Bei jeder anderen unerwarteten Länge sicher + // auf die Defaults zurückfallen. + if resolved.w.count == 19 { + resolved.w.append(contentsOf: [0.0658, 0.1542]) + } + if resolved.w.count != 21 { + resolved.w = Self.defaultW + } + s = resolved + decay = -resolved.w[20] + factor = Self.roundTo(exp((1.0 / decay) * log(0.9)) - 1, 8) + intervalModifier = Self.roundTo((pow(resolved.requestRetention, 1.0 / decay) - 1) / factor, 8) } // MARK: - Public API diff --git a/Tests/UnitTests/FSRSSchedulerTests.swift b/Tests/UnitTests/FSRSSchedulerTests.swift index 9905330..a43bbd7 100644 --- a/Tests/UnitTests/FSRSSchedulerTests.swift +++ b/Tests/UnitTests/FSRSSchedulerTests.swift @@ -84,6 +84,16 @@ struct FSRSSchedulerTests { } } } + + @Test("FSRS-5-Settings (19 Gewichte) crashen nicht, werden migriert") + func fsrs5WeightsMigrateInsteadOfCrash() { + var settings = FSRSSettings() + settings.w = Array(FSRS.defaultW.prefix(19)) // FSRS-5-Format + let fsrs = FSRS(settings) + let next = fsrs.grade(card: .empty(now: Date()), rating: .good, now: Date()) + #expect(next.reps == 1) + #expect(next.state != .new) + } } // swiftlint:enable identifier_name