fix(fsrs): 19-Gewichte (FSRS-5) migrieren statt w[20]-Crash

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) <noreply@anthropic.com>
This commit is contained in:
Till JS 2026-05-25 16:50:05 +02:00
parent cb4bb13d09
commit fbd758d96e
2 changed files with 24 additions and 4 deletions

View file

@ -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

View file

@ -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