CNA-GAP-073: Offline leaderboards keep no Stream columns, and a column changed without a rating change is not saved offline
Evidence basis: source-verified at the pinned commit; tests exist (not executed for this page). Claims on this page were checked by reading the CNA source at commit b0e97bb1; unless a sentence says otherwise, nothing here was built or executed. Nothing on this page was executed unless the Evidence section says so.
Without a gamer-services endpoint, LeaderboardWriter saves an entry only when its rating changes and the local store drops Stream columns, so a column edited alone, or any Stream column, does not survive the session offline.
- Identifier
CNA-GAP-073- Category
- Functional gap
- Subsystem
- Networking & gamer services
- Status
- Open
- Verified against
- CNA
b0e97bb1(b0e97bb1bb876f9b3edd6f4ff1ef3067908ae8ac) - Evidence basis
- Source-verified: read at TARGET, not executed
- Tests touching this area
- Yes: see Current tests
- Affected contract
- LeaderboardWriter and LeaderboardEntry columns (PropertyDictionary) for gamers without a service account (offline local profiles)
Expected behaviour
XNA 4.0's leaderboard PropertyDictionary supports Stream columns (GetValueStream), and a column written during gameplay is part of the entry as much as its rating. CNA's own service path keeps Stream columns (up to 256 bytes) and collects every changed column for the session's EndGame write.
Actual behaviour at TARGET
For a writer without a service account, LeaderboardWriter.cpp installs only a rating-changed hook (SetOnRatingChangedHookEXT) that saves the entry, with its columns, through SaveLeaderboardEntryEXT; changing a column without changing the rating saves nothing. Stream creation is enabled only for service entries (BindServiceEntry), and ColumnsToJson in LocalGamerServicesStore.cpp writes int32, int64, double, single, string, DateTime, TimeSpan and outcome values and skips any other type, so the offline store keeps no Stream column. CNA records both as its own findings: GS-AUDIT-006 in gamer-services-issues.md and section 22 of gamer-services-known-limitations.md ("offline local boards (no service) do not keep Stream columns").
Source locations
modules/gamer-services/src/Xna/LeaderboardWriter.cpp— offline entries persist only through the rating-changed hook; streams only for service entriesmodules/gamer-services/src/Internal/LocalGamerServicesStore.cpp— ColumnsToJson skips Stream and other unsupported valuesaudit/gamer-services/gamer-services-issues.md— GS-AUDIT-006: offline leaderboard column persistencedocs/gamer-services-known-limitations.md— section 22: leaderboard columns
Evidence
Read at b0e97bb1; nothing was built or executed. CNA's audit rates GS-AUDIT-006 as strong evidence and made no implementation change.
Focused reproduction
No focused reproduction is known. Nothing has been invented here; the evidence above is what exists.
Current tests
GamerServicesGamerTests.cpp round-trips offline column types through the store (OfflineIntegerBoundariesRoundTripExactly); no test changes a column alone through LeaderboardWriter offline, and none writes a Stream column offline.
Regression test
Offline, change a column without changing the rating, reload the store and expect the column; write a Stream column offline and expect it back, or a documented refusal.
Blast radius
Games that keep per-player statistics in leaderboard columns on machines without a gamer-services endpoint, or store replay or ghost data in Stream columns there. Service accounts are unaffected.
Workaround
Change the rating together with the columns (for example rewrite it with the same value) so the offline hook saves the entry, and keep Stream data in the game's own storage.
Related pages
The same subject is explained at several altitudes. These are the neighbouring pages at each one.
- Deep dives
- GamerServices contract: leaderboards
- Known issues
- Functional gap index