crossmate

A collaborative crossword app for iOS
Log | Files | Refs | LICENSE

commit 51dea575b98467630bb0174fe041bd62943a3938
parent f851ea71b5e384b526455128cb1f2b299ce513bd
Author: Michael Camilleri <[email protected]>
Date:   Sat, 23 May 2026 18:12:32 +0900

Increase diagnostic detail

Diffstat:
MCrossmate/CrossmateApp.swift | 4++--
MCrossmate/Services/NYTPuzzleUpgrader.swift | 32+++++++++++++++++++++++++-------
2 files changed, 27 insertions(+), 9 deletions(-)

diff --git a/Crossmate/CrossmateApp.swift b/Crossmate/CrossmateApp.swift @@ -458,8 +458,8 @@ private struct PuzzleDisplayView: View { switch outcome { case .upgraded: services.eventLog.note("[upgrade NYT \(gameID.uuidString.prefix(8))] applied") - case .mismatched: - services.eventLog.note("[upgrade NYT \(gameID.uuidString.prefix(8))] structural mismatch — clue not updated", level: "warn") + case .mismatched(let reason): + services.eventLog.note("[upgrade NYT \(gameID.uuidString.prefix(8))] structural mismatch — \(reason)", level: "warn") case .failed(let error): services.eventLog.note("[upgrade NYT \(gameID.uuidString.prefix(8))] fetch failed: \(error)", level: "error") } diff --git a/Crossmate/Services/NYTPuzzleUpgrader.swift b/Crossmate/Services/NYTPuzzleUpgrader.swift @@ -16,8 +16,9 @@ enum NYTPuzzleUpgrader { case upgraded(newSource: String) /// The new XD's grid differs from the persisted one. Per the upgrade /// policy, the caller should stamp the new CmVer but keep the old - /// source so the player's moves stay valid. - case mismatched + /// source so the player's moves stay valid. `reason` describes the + /// first divergence found so diagnostics can pinpoint the cell. + case mismatched(reason: String) /// The fetch or a parse step failed. Caller should leave both the /// source and the CmVer untouched so the upgrade is retried on the /// next launch. @@ -45,7 +46,9 @@ enum NYTPuzzleUpgrader { return .failed(error) } - guard structurallyEquivalent(oldXD, newXD) else { return .mismatched } + if let reason = structuralDivergence(oldXD, newXD) { + return .mismatched(reason: reason) + } return .upgraded(newSource: newSource) } @@ -114,19 +117,34 @@ enum NYTPuzzleUpgrader { /// cell. Special markers, accepted variants, clues, and headers may all /// differ. static func structurallyEquivalent(_ a: XD, _ b: XD) -> Bool { - guard a.width == b.width, a.height == b.height else { return false } + structuralDivergence(a, b) == nil + } + + /// Walks the grid in row-major order and returns a short description of + /// the first cell that disagrees, or nil when the two are equivalent. + /// Used to populate `.mismatched(reason:)` for diagnostic logging. + static func structuralDivergence(_ a: XD, _ b: XD) -> String? { + if a.width != b.width || a.height != b.height { + return "dims old=\(a.width)x\(a.height) new=\(b.width)x\(b.height)" + } for row in 0..<a.height { for col in 0..<a.width { switch (a.cells[row][col], b.cells[row][col]) { case (.block, .block): continue case let (.open(left, _, _), .open(right, _, _)): - if left != right { return false } + if left != right { + return "cell(r=\(row),c=\(col)) old=\(left ?? "nil") new=\(right ?? "nil")" + } + case (.block, .open): + return "cell(r=\(row),c=\(col)) old=block new=open" + case (.open, .block): + return "cell(r=\(row),c=\(col)) old=open new=block" default: - return false + return "cell(r=\(row),c=\(col)) kind mismatch" } } } - return true + return nil } }