commit 45c220c39ad894a415bfe9abe7652be34094fd67
parent c02a4eca9162c4aabd7fd1f2e11d8019a61c4659
Author: Michael Camilleri <[email protected]>
Date: Sat, 27 Jun 2026 07:01:12 +0900
Fold shared clock opens into the Player open burst
Opening a shared puzzle could enqueue a clockOpen Player write just
before the shared open path enqueued read cursor, name and selection for
the same Player record. CKSyncEngine could ship those as separate Player
saves even though the later open burst already carried the
freshly-written time log.
This commit skips the standalone clockOpen enqueue for shared games
while still opening the local solve clock immediately. Solo games keep
their standalone clock enqueue, and shared games let the existing
activateSharing burst send the same Player row with the clock, read
cursor, name, selection and push address together.
Co-Authored-By: Codex GPT 5.5 <[email protected]>
Diffstat:
2 files changed, 18 insertions(+), 0 deletions(-)
diff --git a/Crossmate/Persistence/GameStore.swift b/Crossmate/Persistence/GameStore.swift
@@ -1738,6 +1738,18 @@ final class GameStore {
completedAt(forGame: gameID) != nil
}
+ /// Whether `gameID` is currently shared. Shared open-time Player writes are
+ /// already batched by `PuzzleDisplayView.activateSharing`; solo games still
+ /// need standalone Player enqueues so their clock syncs across this
+ /// account's devices.
+ func isGameShared(gameID: UUID) -> Bool {
+ let request = NSFetchRequest<GameEntity>(entityName: "GameEntity")
+ request.predicate = NSPredicate(format: "id == %@", gameID as CVarArg)
+ request.fetchLimit = 1
+ guard let entity = try? context.fetch(request).first else { return false }
+ return entity.ckShareRecordName != nil || entity.databaseScope == 1
+ }
+
/// Reads, mutates, and re-persists the local author's `Player.timeLog`,
/// creating a stub `PlayerEntity` if none exists (works for solo games — no
/// `isShared` gate). `updatedAt` is left untouched on an existing row: the
diff --git a/Crossmate/Services/SessionCoordinator.swift b/Crossmate/Services/SessionCoordinator.swift
@@ -613,6 +613,12 @@ final class SessionCoordinator {
authorID: localClockAuthorID,
reconcileStale: firstOpenThisLaunch
) else { return }
+ // Shared puzzle opens immediately run `activateSharing`, whose
+ // Player-record burst sends the same row with read cursor, name,
+ // selection, push address, and this freshly-written time log. Avoid a
+ // separate clock enqueue that CKSyncEngine can ship as its own Player
+ // save just before or after the burst.
+ guard !store.isGameShared(gameID: gameID) else { return }
enqueueClockIfSynced(gameID: gameID, reason: "clockOpen")
}